mirror of
https://github.com/jankae/LibreVNA.git
synced 2026-03-27 23:34:50 +01:00
Tracking generator with offset + incomplete automatic source/receiver calibration
This commit is contained in:
parent
026fffd588
commit
5b771e2a86
|
|
@ -110,6 +110,7 @@ QT += widgets
|
|||
FORMS += \
|
||||
Calibration/addamplitudepointsdialog.ui \
|
||||
Calibration/amplitudecaldialog.ui \
|
||||
Calibration/automaticamplitudedialog.ui \
|
||||
Calibration/calibrationtracedialog.ui \
|
||||
Calibration/calkitdialog.ui \
|
||||
CustomWidgets/tilewidget.ui \
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "unit.h"
|
||||
#include <QDebug>
|
||||
#include "ui_addamplitudepointsdialog.h"
|
||||
#include "ui_automaticamplitudedialog.h"
|
||||
#include <QMessageBox>
|
||||
|
||||
using namespace std;
|
||||
|
|
@ -24,7 +25,6 @@ AmplitudeCalDialog::AmplitudeCalDialog(Device *dev, QWidget *parent) :
|
|||
ui->view->setColumnWidth(AmplitudeModel::ColIndexCorrectionFactors, 150);
|
||||
ui->view->setColumnWidth(AmplitudeModel::ColIndexPort1, 150);
|
||||
ui->view->setColumnWidth(AmplitudeModel::ColIndexPort2, 150);
|
||||
connect(dev, &Device::AmplitudeCorrectionPointReceived, this, &AmplitudeCalDialog::ReceivedPoint);
|
||||
connect(ui->load, &QPushButton::clicked, [=](){
|
||||
if(ConfirmActionIfEdited()) {
|
||||
LoadFromDevice();
|
||||
|
|
@ -59,6 +59,7 @@ AmplitudeCalDialog::AmplitudeCalDialog(Device *dev, QWidget *parent) :
|
|||
mode = CalibrationMode::OnlyPort2;
|
||||
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
|
||||
});
|
||||
connect(ui->automatic, &QPushButton::clicked, this, &AmplitudeCalDialog::AutomaticMeasurementDialog);
|
||||
}
|
||||
|
||||
AmplitudeCalDialog::~AmplitudeCalDialog()
|
||||
|
|
@ -108,7 +109,6 @@ void AmplitudeCalDialog::setAmplitude(double amplitude, unsigned int point, bool
|
|||
|
||||
void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
|
||||
{
|
||||
qDebug() << "Received a point";
|
||||
CorrectionPoint c;
|
||||
c.frequency = p.freq * 10.0;
|
||||
c.correctionPort1 = p.port1;
|
||||
|
|
@ -120,13 +120,20 @@ void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
|
|||
points.push_back(c);
|
||||
model.endInsertRows();
|
||||
emit pointsUpdated();
|
||||
if(p.pointNum == p.totalPoints - 1) {
|
||||
// this was the last point
|
||||
disconnect(dev, &Device::AmplitudeCorrectionPointReceived, this, nullptr);
|
||||
ui->load->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::LoadFromDevice()
|
||||
{
|
||||
ui->load->setEnabled(false);
|
||||
dev->SetIdle();
|
||||
RemoveAllPoints();
|
||||
qDebug() << "Asking for amplitude calibration";
|
||||
connect(dev, &Device::AmplitudeCorrectionPointReceived, this, &AmplitudeCalDialog::ReceivedPoint);
|
||||
dev->SendCommandWithoutPayload(requestCommand());
|
||||
edited = false;
|
||||
ui->save->setEnabled(false);
|
||||
|
|
@ -239,6 +246,119 @@ void AmplitudeCalDialog::AddPointDialog()
|
|||
// aborted, nothing to do
|
||||
delete d;
|
||||
});
|
||||
|
||||
dev->SendCommandWithoutPayload(requestCommand());
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::AutomaticMeasurementDialog()
|
||||
{
|
||||
bool isSourceCal = pointType() == Protocol::PacketType::SourceCalPoint;
|
||||
const QString ownCal = isSourceCal ? "Source" : "Receiver";
|
||||
const QString otherCal = isSourceCal ? "Receiver" : "Source";
|
||||
|
||||
// compile info text
|
||||
QString info = "It is possible to determine the " + ownCal + " Calibration if a valid " + otherCal
|
||||
+ " Calibration is already present. To do so, connect a short cable with as little loss as possible"
|
||||
+ " between the ports. For each point in the " + otherCal + " Calibration, the device will take"
|
||||
+ " a measurement and determine the correction factors for the " + ownCal + " Calibration.";
|
||||
|
||||
auto d = new QDialog(this);
|
||||
auto ui = new Ui::AutomaticAmplitudeDialog();
|
||||
ui->setupUi(d);
|
||||
ui->explanation->setText(info);
|
||||
ui->status->setText("Gathering information about "+otherCal+" Calibration...");
|
||||
|
||||
automatic.points.clear();
|
||||
|
||||
connect(d, &QDialog::rejected, ui->abort, &QPushButton::click);
|
||||
connect(ui->abort, &QPushButton::clicked, [=](){
|
||||
// aborted, clean up
|
||||
delete d;
|
||||
});
|
||||
|
||||
dev->SetIdle();
|
||||
auto receiveConn = connect(dev, &Device::AmplitudeCorrectionPointReceived, this, [this, ui, otherCal](Protocol::AmplitudeCorrectionPoint p) {
|
||||
CorrectionPoint c;
|
||||
c.frequency = p.freq * 10.0;
|
||||
c.correctionPort1 = p.port1;
|
||||
c.correctionPort2 = p.port2;
|
||||
c.port1set = true;
|
||||
c.port2set = true;
|
||||
automatic.points.push_back(c);
|
||||
ui->progress->setValue(100 * (p.pointNum+1) / p.totalPoints);
|
||||
if(p.pointNum == p.totalPoints - 1) {
|
||||
// this was the last point, indicate ready for measurement
|
||||
ui->progress->setValue(0);
|
||||
ui->status->setText(otherCal + " Calibration contains " +QString::number(p.totalPoints)+" points, ready to start measurement");
|
||||
ui->start->setEnabled(true);
|
||||
disconnect(dev, &Device::AmplitudeCorrectionPointReceived, this, nullptr);
|
||||
}
|
||||
});
|
||||
// request points of otherCal
|
||||
// switch between source/receiver calibration
|
||||
auto request = isSourceCal ? Protocol::PacketType::RequestReceiverCal : Protocol::PacketType::RequestSourceCal;
|
||||
dev->SendCommandWithoutPayload(request);
|
||||
|
||||
connect(ui->start, &QPushButton::clicked, [=](){
|
||||
// remove any exising points in own calibration and copy points from other calibration
|
||||
RemoveAllPoints();
|
||||
for(auto p : automatic.points) {
|
||||
AddPoint(p.frequency);
|
||||
}
|
||||
// intialize measurement state machine
|
||||
automatic.resultConnection = connect(dev, &Device::SpectrumResultReceived, [=](Protocol::SpectrumAnalyzerResult res) {
|
||||
if(res.pointNum != 1) {
|
||||
// ignore first and last point, only use the middle one
|
||||
return;
|
||||
}
|
||||
if(automatic.settlingCount > 0) {
|
||||
automatic.settlingCount--;
|
||||
return;
|
||||
}
|
||||
// Grab correct measurement
|
||||
double measurement;
|
||||
if(isSourceCal) {
|
||||
// For a source calibration we need the measurement of the other port (measuring the ouput power of the port to calibrate)
|
||||
measurement = automatic.measuringPort2 ? res.port1 : res.port2;
|
||||
} else {
|
||||
// For a receiver calibration we need the measurement of the port to calibrate
|
||||
measurement = automatic.measuringPort2 ? res.port2 : res.port1;
|
||||
}
|
||||
// convert to dbm
|
||||
double reported_dbm = 20*log10(measurement);
|
||||
if(isSourceCal) {
|
||||
// receiver cal already done, the measurement result is accurate and can be used to determine actual output power
|
||||
setAmplitude(reported_dbm, automatic.measuringCount, automatic.measuringPort2);
|
||||
} else {
|
||||
// source cal already done, the output power is accurate while the measurement might be off
|
||||
setAmplitude(excitationAmplitude, automatic.measuringCount, automatic.measuringPort2);
|
||||
}
|
||||
// advance state machine
|
||||
// switch ports
|
||||
automatic.measuringPort2 = !automatic.measuringPort2;
|
||||
if(!automatic.measuringPort2) {
|
||||
// now measuring port1, this means we have to advance to the next point
|
||||
automatic.measuringCount++;
|
||||
if(automatic.measuringCount >= points.size()) {
|
||||
// all done, disconnect this lambda and close dialog
|
||||
disconnect(automatic.resultConnection);
|
||||
delete d;
|
||||
dev->SetIdle();
|
||||
} else {
|
||||
// update progress bar
|
||||
ui->progress->setValue(100 * automatic.measuringCount / points.size());
|
||||
// Start next measurement
|
||||
SetupNextAutomaticPoint(isSourceCal);
|
||||
}
|
||||
}
|
||||
});
|
||||
automatic.measuringPort2 = false;
|
||||
automatic.measuringCount = 0;
|
||||
SetupNextAutomaticPoint(isSourceCal);
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +391,39 @@ void AmplitudeCalDialog::UpdateSaveButton()
|
|||
ui->save->setEnabled(enable);
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::SetupNextAutomaticPoint(bool isSourceCal)
|
||||
{
|
||||
auto point = automatic.points[automatic.measuringCount];
|
||||
Protocol::PacketInfo p = {};
|
||||
p.type = Protocol::PacketType::SpectrumAnalyzerSettings;
|
||||
p.spectrumSettings.RBW = 10000;
|
||||
p.spectrumSettings.UseDFT = 0;
|
||||
// setup 3 points centered around the measurement frequency (zero span not supported yet)
|
||||
p.spectrumSettings.f_stop = point.frequency + 1.0;
|
||||
p.spectrumSettings.f_start = point.frequency - 1.0;
|
||||
p.spectrumSettings.pointNum = 3;
|
||||
p.spectrumSettings.Detector = 0;
|
||||
p.spectrumSettings.SignalID = 1;
|
||||
p.spectrumSettings.WindowType = 3;
|
||||
p.spectrumSettings.trackingGenerator = 1;
|
||||
p.spectrumSettings.trackingPower = excitationAmplitude * 100.0;
|
||||
p.spectrumSettings.trackingGeneratorOffset = 0;
|
||||
// For a source cal, the tracking generator has to be enabled at the measurement port (calibrating the output power)
|
||||
// For a receiver cal, the tracking generator has to be enabled at the other port (calibrating received power)
|
||||
p.spectrumSettings.trackingGeneratorPort = isSourceCal ? automatic.measuringPort2 : !automatic.measuringPort2;
|
||||
if(isSourceCal) {
|
||||
// Calibrating the source which means the receiver is already calibrated -> use receiver corrections but no source corrections
|
||||
p.spectrumSettings.applyReceiverCorrection = 1;
|
||||
p.spectrumSettings.applySourceCorrection = 0;
|
||||
} else {
|
||||
// the other way around
|
||||
p.spectrumSettings.applyReceiverCorrection = 0;
|
||||
p.spectrumSettings.applySourceCorrection = 1;
|
||||
}
|
||||
automatic.settlingCount = 30;
|
||||
dev->SendPacket(p);
|
||||
}
|
||||
|
||||
AmplitudeCalDialog::CalibrationMode AmplitudeCalDialog::getMode() const
|
||||
{
|
||||
return mode;
|
||||
|
|
|
|||
|
|
@ -74,10 +74,13 @@ protected slots:
|
|||
void RemoveAllPoints();
|
||||
void AddPoint(double frequency);
|
||||
void AddPointDialog();
|
||||
void AutomaticMeasurementDialog();
|
||||
signals:
|
||||
void pointsUpdated();
|
||||
void newPointCreated(CorrectionPoint& p);
|
||||
protected:
|
||||
static constexpr double excitationAmplitude = -20.0;
|
||||
|
||||
bool ConfirmActionIfEdited();
|
||||
void UpdateSaveButton();
|
||||
virtual Protocol::PacketType requestCommand() = 0;
|
||||
|
|
@ -95,6 +98,15 @@ protected:
|
|||
AmplitudeModel model;
|
||||
bool edited;
|
||||
CalibrationMode mode;
|
||||
|
||||
void SetupNextAutomaticPoint(bool isSourceCal);
|
||||
struct {
|
||||
std::vector<CorrectionPoint> points;
|
||||
bool measuringPort2; // true if port2 is being calibrated
|
||||
unsigned int measuringCount; // number of calibration point
|
||||
unsigned int settlingCount; // number of measurements still to ignore before taking measurement
|
||||
QMetaObject::Connection resultConnection;
|
||||
} automatic;
|
||||
};
|
||||
|
||||
#endif // SOURCECALDIALOG_H
|
||||
|
|
|
|||
|
|
@ -182,6 +182,13 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="automatic">
|
||||
<property name="text">
|
||||
<string>Automatic measurement</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
|
|||
114
Software/PC_Application/Calibration/automaticamplitudedialog.ui
Normal file
114
Software/PC_Application/Calibration/automaticamplitudedialog.ui
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AutomaticAmplitudeDialog</class>
|
||||
<widget class="QDialog" name="AutomaticAmplitudeDialog">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>575</width>
|
||||
<height>215</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Automatic Measurement</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="explanation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progress">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Status:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="status">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="abort">
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="start">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start Measurement</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/icons/play.png</normaloff>:/icons/play.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
@ -14,10 +14,10 @@ ReceiverCalDialog::ReceiverCalDialog(Device *dev)
|
|||
});
|
||||
}
|
||||
|
||||
void ReceiverCalDialog::SelectedPoint(double frequency, bool port2)
|
||||
void ReceiverCalDialog::SelectedPoint(double frequency, bool)
|
||||
{
|
||||
if(frequency > 0) {
|
||||
Protocol::PacketInfo p;
|
||||
Protocol::PacketInfo p = {};
|
||||
p.type = Protocol::PacketType::SpectrumAnalyzerSettings;
|
||||
p.spectrumSettings.RBW = 10000;
|
||||
p.spectrumSettings.UseDFT = 0;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ protected:
|
|||
void SelectedPoint(double frequency, bool port2) override;
|
||||
void AmplitudeChanged(CorrectionPoint &point, bool port2) override;
|
||||
void UpdateAmplitude(CorrectionPoint& point) override;
|
||||
private:
|
||||
static constexpr double excitationAmplitude = -20.0;
|
||||
};
|
||||
|
||||
#endif // SOURCECALDIALOG_H
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include <QDesktopWidget>
|
||||
#include <QApplication>
|
||||
#include <QActionGroup>
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
|
||||
SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window)
|
||||
: Mode(window, "Spectrum Analyzer"),
|
||||
|
|
@ -176,6 +177,49 @@ SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window)
|
|||
window->addToolBar(tb_acq);
|
||||
toolbars.insert(tb_acq);
|
||||
|
||||
// Tracking generator toolbar
|
||||
auto tb_trackgen = new QToolBar("Tracking Generator");
|
||||
auto cbTrackGenEnable = new QCheckBox("Tracking Generator");
|
||||
connect(cbTrackGenEnable, &QCheckBox::toggled, [=](bool enabled) {
|
||||
settings.trackingGenerator = enabled;
|
||||
SettingsChanged();
|
||||
});
|
||||
tb_trackgen->addWidget(cbTrackGenEnable);
|
||||
|
||||
auto cbTrackGenPort = new QComboBox();
|
||||
cbTrackGenPort->addItem("Port 1");
|
||||
cbTrackGenPort->addItem("Port 2");
|
||||
cbTrackGenPort->setCurrentIndex(0);
|
||||
connect(cbTrackGenPort, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {
|
||||
settings.trackingGeneratorPort = index;
|
||||
if(settings.trackingGenerator) {
|
||||
SettingsChanged();
|
||||
}
|
||||
});
|
||||
tb_trackgen->addWidget(cbTrackGenPort);
|
||||
|
||||
auto dbm = new QDoubleSpinBox();
|
||||
dbm->setFixedWidth(95);
|
||||
dbm->setRange(-100.0, 100.0);
|
||||
dbm->setSingleStep(0.25);
|
||||
dbm->setSuffix("dbm");
|
||||
dbm->setToolTip("Level");
|
||||
dbm->setKeyboardTracking(false);
|
||||
connect(dbm, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &SpectrumAnalyzer::SetTGLevel);
|
||||
connect(this, &SpectrumAnalyzer::TGLevelChanged, dbm, &QDoubleSpinBox::setValue);
|
||||
tb_trackgen->addWidget(new QLabel("Level:"));
|
||||
tb_trackgen->addWidget(dbm);
|
||||
|
||||
auto tgOffset = new SIUnitEdit("Hz", " kMG", 6);
|
||||
tgOffset->setFixedWidth(100);
|
||||
tgOffset->setToolTip("Tracking generator offset");
|
||||
connect(tgOffset, &SIUnitEdit::valueChanged, this, &SpectrumAnalyzer::SetTGOffset);
|
||||
connect(this, &SpectrumAnalyzer::TGOffsetChanged, tgOffset, &SIUnitEdit::setValueQuiet);
|
||||
tb_trackgen->addWidget(new QLabel("Offset:"));
|
||||
tb_trackgen->addWidget(tgOffset);
|
||||
|
||||
window->addToolBar(tb_trackgen);
|
||||
toolbars.insert(tb_trackgen);
|
||||
|
||||
markerModel = new TraceMarkerModel(traceModel);
|
||||
|
||||
|
|
@ -192,6 +236,11 @@ SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window)
|
|||
window->addDockWidget(Qt::BottomDockWidgetArea, markerDock);
|
||||
docks.insert(markerDock);
|
||||
|
||||
|
||||
// Set initial TG settings
|
||||
SetTGLevel(-20.0);
|
||||
SetTGOffset(0);
|
||||
|
||||
// Set initial sweep settings
|
||||
auto pref = Preferences::getInstance();
|
||||
if(pref.Startup.RememberSweepSettings) {
|
||||
|
|
@ -246,9 +295,11 @@ void SpectrumAnalyzer::SettingsChanged()
|
|||
settings.pointNum = settings.f_stop - settings.f_start + 1;
|
||||
}
|
||||
settings.applyReceiverCorrection = 1;
|
||||
settings.applySourceCorrection = 1;
|
||||
|
||||
auto pref = Preferences::getInstance();
|
||||
if(pref.Acquisition.useDFTinSAmode && settings.RBW <= pref.Acquisition.RBWLimitForDFT) {
|
||||
if(!settings.trackingGenerator && pref.Acquisition.useDFTinSAmode && settings.RBW <= pref.Acquisition.RBWLimitForDFT) {
|
||||
// Enable DFT if below RBW threshold and TG is not enabled
|
||||
settings.UseDFT = 1;
|
||||
} else {
|
||||
settings.UseDFT = 0;
|
||||
|
|
@ -372,6 +423,30 @@ void SpectrumAnalyzer::SetAveraging(unsigned int averages)
|
|||
SettingsChanged();
|
||||
}
|
||||
|
||||
void SpectrumAnalyzer::SetTGLevel(double level)
|
||||
{
|
||||
if(level > Device::Info().limits_cdbm_max / 100.0) {
|
||||
level = Device::Info().limits_cdbm_max / 100.0;
|
||||
} else if(level < Device::Info().limits_cdbm_min / 100.0) {
|
||||
level = Device::Info().limits_cdbm_min / 100.0;
|
||||
}
|
||||
emit TGLevelChanged(level);
|
||||
settings.trackingPower = level * 100;
|
||||
if(settings.trackingGenerator) {
|
||||
SettingsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumAnalyzer::SetTGOffset(double offset)
|
||||
{
|
||||
settings.trackingGeneratorOffset = offset;
|
||||
|
||||
ConstrainAndUpdateFrequencies();
|
||||
if(settings.trackingGenerator) {
|
||||
SettingsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumAnalyzer::UpdateAverageCount()
|
||||
{
|
||||
lAverages->setText(QString::number(average.getLevel()) + "/");
|
||||
|
|
@ -388,10 +463,25 @@ void SpectrumAnalyzer::ConstrainAndUpdateFrequencies()
|
|||
if(settings.f_start < Device::Info().limits_minFreq) {
|
||||
settings.f_start = Device::Info().limits_minFreq;
|
||||
}
|
||||
|
||||
bool trackingOffset_limited = false;
|
||||
if(settings.f_stop + settings.trackingGeneratorOffset > Device::Info().limits_maxFreq) {
|
||||
trackingOffset_limited = true;
|
||||
settings.trackingGeneratorOffset = Device::Info().limits_maxFreq - settings.f_stop;
|
||||
}
|
||||
if(settings.f_start + settings.trackingGeneratorOffset < Device::Info().limits_minFreq) {
|
||||
trackingOffset_limited = true;
|
||||
settings.trackingGeneratorOffset = Device::Info().limits_minFreq - settings.f_start;
|
||||
}
|
||||
if(trackingOffset_limited) {
|
||||
InformationBox::ShowMessage("Warning", "The selected tracking generator offset is not reachable for all frequencies with the current span. "
|
||||
"The tracking generator offset has been constrained according to the selected start and stop frequencies");
|
||||
}
|
||||
emit startFreqChanged(settings.f_start);
|
||||
emit stopFreqChanged(settings.f_stop);
|
||||
emit spanChanged(settings.f_stop - settings.f_start);
|
||||
emit centerFreqChanged((settings.f_stop + settings.f_start)/2);
|
||||
emit TGOffsetChanged(settings.trackingGeneratorOffset);
|
||||
SettingsChanged();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,9 @@ private slots:
|
|||
// Acquisition control
|
||||
void SetRBW(double bandwidth);
|
||||
void SetAveraging(unsigned int averages);
|
||||
|
||||
signals:
|
||||
// TG control
|
||||
void SetTGLevel(double level);
|
||||
void SetTGOffset(double offset);
|
||||
|
||||
private:
|
||||
void UpdateAverageCount();
|
||||
|
|
@ -59,6 +60,8 @@ signals:
|
|||
void centerFreqChanged(double freq);
|
||||
void spanChanged(double span);
|
||||
void RBWChanged(double RBW);
|
||||
void TGOffsetChanged(double offset);
|
||||
void TGLevelChanged(double level);
|
||||
|
||||
void averagingChanged(unsigned int averages);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ public:
|
|||
LiveParameter liveParameter() { return _liveParam; }
|
||||
LivedataType liveType() { return _liveType; }
|
||||
unsigned int size() { return _data.size(); }
|
||||
double minFreq() { return _data.front().frequency; };
|
||||
double maxFreq() { return _data.back().frequency; };
|
||||
double minFreq() { return size() > 0 ? _data.front().frequency : 0.0; };
|
||||
double maxFreq() { return size() > 0 ? _data.back().frequency : 0.0; };
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ QString TraceMarker::readableData()
|
|||
auto freqDiff = frequency - delta->frequency;
|
||||
auto valueDiff = data / delta->data;
|
||||
auto phase = arg(valueDiff);
|
||||
return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -407,6 +407,11 @@ static Protocol::SpectrumAnalyzerSettings DecodeSpectrumAnalyzerSettings(uint8_t
|
|||
d.Detector = e.getBits(3);
|
||||
d.UseDFT = e.getBits(1);
|
||||
d.applyReceiverCorrection = e.getBits(1);
|
||||
d.trackingGenerator = e.getBits(1);
|
||||
d.applySourceCorrection = e.getBits(1);
|
||||
d.trackingGeneratorPort = e.getBits(1);
|
||||
e.get(d.trackingGeneratorOffset);
|
||||
e.get(d.trackingPower);
|
||||
return d;
|
||||
}
|
||||
static int16_t EncodeSpectrumAnalyzerSettings(Protocol::SpectrumAnalyzerSettings d, uint8_t *buf,
|
||||
|
|
@ -421,6 +426,11 @@ static int16_t EncodeSpectrumAnalyzerSettings(Protocol::SpectrumAnalyzerSettings
|
|||
e.addBits(d.Detector, 3);
|
||||
e.addBits(d.UseDFT, 1);
|
||||
e.addBits(d.applyReceiverCorrection, 1);
|
||||
e.addBits(d.trackingGenerator, 1);
|
||||
e.addBits(d.applySourceCorrection, 1);
|
||||
e.addBits(d.trackingGeneratorPort, 1);
|
||||
e.add(d.trackingGeneratorOffset);
|
||||
e.add(d.trackingPower);
|
||||
return e.getSize();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,11 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings {
|
|||
uint8_t Detector :3;
|
||||
uint8_t UseDFT :1;
|
||||
uint8_t applyReceiverCorrection :1;
|
||||
uint8_t trackingGenerator :1;
|
||||
uint8_t applySourceCorrection :1;
|
||||
uint8_t trackingGeneratorPort :1; // 0 for port1, 1 for port2
|
||||
int64_t trackingGeneratorOffset;
|
||||
int16_t trackingPower;
|
||||
};
|
||||
|
||||
using SpectrumAnalyzerResult = struct _spectrumAnalyzerResult {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
#include "Si5351C.hpp"
|
||||
#include "AmplitudeCal.hpp"
|
||||
|
||||
static constexpr uint32_t BandSwitchFrequency = 25000000;
|
||||
|
||||
void Generator::Setup(Protocol::GeneratorSettings g) {
|
||||
if(g.activePort == 0) {
|
||||
// both ports disabled, no need to configure PLLs
|
||||
|
|
@ -45,29 +43,22 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
|
|||
g.cdbm_level += correction.port2;
|
||||
}
|
||||
}
|
||||
auto amplitude = HW::GetAmplitudeSettings(g.cdbm_level, g.frequency, g.applyAmplitudeCorrection, g.activePort == 2);
|
||||
// Select correct source
|
||||
if(g.frequency < BandSwitchFrequency) {
|
||||
if(g.frequency < HW::BandSwitchFrequency) {
|
||||
m.SourceLowEN = 1;
|
||||
m.SourceLowFrequency = g.frequency;
|
||||
m.SourceHighCE = 0;
|
||||
m.SourceHighRFEN = 0;
|
||||
m.SourceHighFrequency = BandSwitchFrequency;
|
||||
m.SourceHighFrequency = HW::BandSwitchFrequency;
|
||||
m.SourceHighLowpass = (int) FPGA::LowpassFilter::M947;
|
||||
m.SourceHighPower = (int) MAX2871::Power::n4dbm;
|
||||
m.SourceHighband = false;
|
||||
if(g.cdbm_level <= HW::LowBandMinPower) {
|
||||
// can use the low power setting
|
||||
m.SourceLowPower = (int) Si5351C::DriveStrength::mA2;
|
||||
g.cdbm_level -= HW::LowBandMinPower;
|
||||
} else {
|
||||
// needs the high power setting
|
||||
m.SourceLowPower = (int) Si5351C::DriveStrength::mA8;
|
||||
g.cdbm_level -= HW::LowBandMaxPower;
|
||||
}
|
||||
m.SourceLowPower = (int) amplitude.lowBandPower;
|
||||
m.SourceHighPower = (int) MAX2871::Power::n4dbm;
|
||||
} else {
|
||||
m.SourceLowEN = 0;
|
||||
m.SourceLowFrequency = BandSwitchFrequency;
|
||||
m.SourceLowFrequency = HW::BandSwitchFrequency;
|
||||
m.SourceHighCE = 1;
|
||||
m.SourceHighRFEN = 1;
|
||||
m.SourceHighFrequency = g.frequency;
|
||||
|
|
@ -81,26 +72,10 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
|
|||
m.SourceHighLowpass = (int) FPGA::LowpassFilter::None;
|
||||
}
|
||||
m.SourceHighband = true;
|
||||
if(g.cdbm_level <= HW::HighBandMinPower) {
|
||||
// can use the low power setting
|
||||
m.SourceHighPower = (int) MAX2871::Power::n4dbm;
|
||||
g.cdbm_level -= HW::HighBandMinPower;
|
||||
} else {
|
||||
// needs the high power setting
|
||||
m.SourceHighPower = (int) MAX2871::Power::p5dbm;
|
||||
g.cdbm_level -= HW::HighBandMaxPower;
|
||||
}
|
||||
m.SourceHighPower = (int) amplitude.highBandPower;
|
||||
m.SourceLowPower = (int) MAX2871::Power::n4dbm;
|
||||
}
|
||||
|
||||
// calculate required attenuation
|
||||
int16_t attval = -g.cdbm_level / 25;
|
||||
// TODO set some flag if attenuator limit reached?
|
||||
if(attval > 127) {
|
||||
attval = 127;
|
||||
} else if(attval < 0) {
|
||||
attval = 0;
|
||||
}
|
||||
m.attenuator = attval;
|
||||
m.attenuator = amplitude.attenuator;
|
||||
Manual::Setup(m);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,6 +223,52 @@ void HW::SetIdle() {
|
|||
FPGA::Enable(FPGA::Periphery::PortSwitch, false);
|
||||
}
|
||||
|
||||
HW::AmplitudeSettings HW::GetAmplitudeSettings(int16_t cdbm, uint64_t freq, bool applyCorrections, bool port2) {
|
||||
if (applyCorrections) {
|
||||
auto correction = AmplitudeCal::SourceCorrection(freq);
|
||||
if (port2) {
|
||||
cdbm += correction.port2;
|
||||
} else {
|
||||
cdbm += correction.port1;
|
||||
}
|
||||
}
|
||||
AmplitudeSettings ret;
|
||||
if(freq < BandSwitchFrequency) {
|
||||
if(cdbm <= HW::LowBandMinPower) {
|
||||
// can use the low power setting
|
||||
ret.lowBandPower = Si5351C::DriveStrength::mA2;
|
||||
cdbm -= HW::LowBandMinPower;
|
||||
} else {
|
||||
// needs the high power setting
|
||||
ret.lowBandPower = Si5351C::DriveStrength::mA8;
|
||||
cdbm -= HW::LowBandMaxPower;
|
||||
}
|
||||
} else {
|
||||
if(cdbm <= HW::HighBandMinPower) {
|
||||
// can use the low power setting
|
||||
ret.highBandPower = MAX2871::Power::n4dbm;
|
||||
cdbm -= HW::HighBandMinPower;
|
||||
} else {
|
||||
// needs the high power setting
|
||||
ret.highBandPower = MAX2871::Power::p5dbm;
|
||||
cdbm -= HW::HighBandMaxPower;
|
||||
}
|
||||
}
|
||||
// calculate required attenuation
|
||||
int16_t attval = -cdbm / 25;
|
||||
if(attval > 127) {
|
||||
attval = 127;
|
||||
ret.unlevel = true;
|
||||
} else if(attval < 0) {
|
||||
attval = 0;
|
||||
ret.unlevel = true;
|
||||
} else {
|
||||
ret.unlevel = false;
|
||||
}
|
||||
ret.attenuator = attval;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HW::fillDeviceInfo(Protocol::DeviceInfo *info, bool updateEvenWhenBusy) {
|
||||
// copy constant default values
|
||||
memcpy(info, &HW::Info, sizeof(HW::Info));
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#include "Protocol.hpp"
|
||||
#include "FPGA/FPGA.hpp"
|
||||
#include "AmplitudeCal.hpp"
|
||||
#include "max2871.hpp"
|
||||
#include "Si5351C.hpp"
|
||||
|
||||
#define USE_DEBUG_PINS
|
||||
|
||||
|
|
@ -33,6 +35,7 @@ static constexpr uint32_t LO1_minFreq = 25000000;
|
|||
static constexpr uint32_t MaxSamples = 130944;
|
||||
static constexpr uint32_t MinSamples = 16;
|
||||
static constexpr uint32_t PLLRef = 100000000;
|
||||
static constexpr uint32_t BandSwitchFrequency = 25000000;
|
||||
|
||||
static constexpr uint8_t ADCprescaler = FPGA::Clockrate / ADCSamplerate;
|
||||
static_assert(ADCprescaler * ADCSamplerate == FPGA::Clockrate, "ADCSamplerate can not be reached exactly");
|
||||
|
|
@ -84,6 +87,16 @@ void SetMode(Mode mode);
|
|||
void SetIdle();
|
||||
void Work();
|
||||
|
||||
using AmplitudeSettings = struct _amplitudeSettings {
|
||||
uint8_t attenuator;
|
||||
union {
|
||||
MAX2871::Power highBandPower;
|
||||
Si5351C::DriveStrength lowBandPower;
|
||||
};
|
||||
bool unlevel;
|
||||
};
|
||||
AmplitudeSettings GetAmplitudeSettings(int16_t cdbm, uint64_t freq = 0, bool applyCorrections = false, bool port2 = false);
|
||||
|
||||
bool GetTemps(uint8_t *source, uint8_t *lo);
|
||||
void fillDeviceInfo(Protocol::DeviceInfo *info, bool updateEvenWhenBusy = false);
|
||||
namespace Ref {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ static float port1Measurement[FPGA::DFTbins], port2Measurement[FPGA::DFTbins];
|
|||
|
||||
static uint8_t signalIDsteps;
|
||||
static std::array<uint8_t, 4> signalIDprescalers;
|
||||
static uint8_t attenuator;
|
||||
static int64_t trackingFreq;
|
||||
static bool trackingLowband;
|
||||
|
||||
static void StartNextSample() {
|
||||
uint64_t freq = s.f_start + (s.f_stop - s.f_start) * pointCnt / (points - 1);
|
||||
|
|
@ -50,6 +53,34 @@ static void StartNextSample() {
|
|||
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, 112);
|
||||
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, 1120);
|
||||
negativeDFT = true;
|
||||
|
||||
// set tracking generator to default values
|
||||
trackingFreq = 0;
|
||||
trackingLowband = false;
|
||||
attenuator = 0;
|
||||
// this is the first step in each point, update tracking generator if enabled
|
||||
if (s.trackingGenerator) {
|
||||
trackingFreq = freq - s.trackingGeneratorOffset;
|
||||
if(trackingFreq > 0 && trackingFreq <= (int64_t) HW::Info.limits_maxFreq) {
|
||||
// tracking frequency is valid, calculate required settings and select band
|
||||
auto amplitude = HW::GetAmplitudeSettings(s.trackingPower, trackingFreq, s.applySourceCorrection, s.trackingGeneratorPort);
|
||||
attenuator = amplitude.attenuator;
|
||||
if(trackingFreq < HW::BandSwitchFrequency) {
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::B, amplitude.lowBandPower);
|
||||
FPGA::Disable(FPGA::Periphery::SourceChip);
|
||||
FPGA::Disable(FPGA::Periphery::SourceRF);
|
||||
trackingLowband = true;
|
||||
} else {
|
||||
Source.SetFrequency(trackingFreq);
|
||||
Source.SetPowerOutA(amplitude.highBandPower, true);
|
||||
FPGA::Enable(FPGA::Periphery::SourceChip);
|
||||
FPGA::Enable(FPGA::Periphery::SourceRF);
|
||||
trackingLowband = false;
|
||||
// selected power potentially changed, update default registers
|
||||
FPGA::WriteMAX2871Default(Source.GetRegisters());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
LO2freq = HW::IF1 - HW::IF2;
|
||||
|
|
@ -121,9 +152,9 @@ static void StartNextSample() {
|
|||
}
|
||||
|
||||
// Configure the sampling in the FPGA
|
||||
FPGA::WriteSweepConfig(0, 0, Source.GetRegisters(), LO1.GetRegisters(), 0,
|
||||
0, FPGA::SettlingTime::us20, FPGA::Samples::SPPRegister, 0,
|
||||
FPGA::LowpassFilter::M947);
|
||||
FPGA::WriteSweepConfig(0, trackingLowband, Source.GetRegisters(), LO1.GetRegisters(), attenuator,
|
||||
trackingFreq, FPGA::SettlingTime::us20, FPGA::Samples::SPPRegister, 0,
|
||||
FPGA::LowpassFilter::Auto);
|
||||
|
||||
FPGA::StartSweep();
|
||||
}
|
||||
|
|
@ -162,10 +193,18 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
|
|||
// enable the required hardware resources
|
||||
Si5351.Enable(SiChannel::Port1LO2);
|
||||
Si5351.Enable(SiChannel::Port2LO2);
|
||||
if (s.trackingGenerator) {
|
||||
Si5351.Enable(SiChannel::LowbandSource);
|
||||
} else {
|
||||
Si5351.Disable(SiChannel::LowbandSource);
|
||||
}
|
||||
FPGA::SetWindow((FPGA::Window) s.WindowType);
|
||||
FPGA::Enable(FPGA::Periphery::LO1Chip);
|
||||
FPGA::Enable(FPGA::Periphery::LO1RF);
|
||||
FPGA::Enable(FPGA::Periphery::ExcitePort1);
|
||||
FPGA::Enable(FPGA::Periphery::ExcitePort1, s.trackingGeneratorPort == 0);
|
||||
FPGA::Enable(FPGA::Periphery::ExcitePort2, s.trackingGeneratorPort == 1);
|
||||
FPGA::Enable(FPGA::Periphery::PortSwitch, s.trackingGenerator);
|
||||
FPGA::Enable(FPGA::Periphery::Amplifier, s.trackingGenerator);
|
||||
FPGA::Enable(FPGA::Periphery::Port1Mixer);
|
||||
FPGA::Enable(FPGA::Periphery::Port2Mixer);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ static constexpr uint16_t IFTableNumEntries = 500;
|
|||
static IFTableEntry IFTable[IFTableNumEntries];
|
||||
static uint16_t IFTableIndexCnt = 0;
|
||||
|
||||
static constexpr uint32_t BandSwitchFrequency = 25000000;
|
||||
static constexpr float alternativeSamplerate = 914285.7143f;
|
||||
static constexpr uint8_t alternativePrescaler = 102400000UL / alternativeSamplerate;
|
||||
static_assert(alternativePrescaler * alternativeSamplerate == 102400000UL, "alternative ADCSamplerate can not be reached exactly");
|
||||
|
|
@ -115,7 +114,7 @@ bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) {
|
|||
bool needs_halt = false;
|
||||
uint64_t actualSourceFreq;
|
||||
bool lowband = false;
|
||||
if (freq < BandSwitchFrequency) {
|
||||
if (freq < HW::BandSwitchFrequency) {
|
||||
needs_halt = true;
|
||||
lowband = true;
|
||||
actualSourceFreq = freq;
|
||||
|
|
@ -290,7 +289,7 @@ void VNA::SweepHalted() {
|
|||
+ (settings.f_stop - settings.f_start) * pointCnt
|
||||
/ (settings.points - 1);
|
||||
bool adcShiftRequired = false;
|
||||
if (frequency < BandSwitchFrequency) {
|
||||
if (frequency < HW::BandSwitchFrequency) {
|
||||
// need the Si5351 as Source
|
||||
Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B,
|
||||
sourceHighPower ? Si5351C::DriveStrength::mA8 : Si5351C::DriveStrength::mA4);
|
||||
|
|
|
|||
Loading…
Reference in a new issue