Working source and receiver calibration

This commit is contained in:
Jan Käberich 2020-11-17 23:03:13 +01:00
parent 875f3b0170
commit 026fffd588
23 changed files with 722 additions and 68 deletions

View File

@ -7,6 +7,7 @@ HEADERS += \
Calibration/calkitdialog.h \
Calibration/json.hpp \
Calibration/measurementmodel.h \
Calibration/receivercaldialog.h \
Calibration/sourcecaldialog.h \
CustomWidgets/colorpickerbutton.h \
CustomWidgets/informationbox.h \
@ -56,6 +57,7 @@ SOURCES += \
Calibration/calkit.cpp \
Calibration/calkitdialog.cpp \
Calibration/measurementmodel.cpp \
Calibration/receivercaldialog.cpp \
Calibration/sourcecaldialog.cpp \
CustomWidgets/colorpickerbutton.cpp \
CustomWidgets/informationbox.cpp \
@ -106,6 +108,7 @@ win32:LIBS += -LC:\Qwt-6.1.4\lib -lqwt
QT += widgets
FORMS += \
Calibration/addamplitudepointsdialog.ui \
Calibration/amplitudecaldialog.ui \
Calibration/calibrationtracedialog.ui \
Calibration/calkitdialog.ui \

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddAmplitudePointsDialog</class>
<widget class="QDialog" name="AddAmplitudePointsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>564</width>
<height>139</height>
</rect>
</property>
<property name="windowTitle">
<string>Add points</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,1">
<item>
<widget class="QRadioButton" name="singlePoint">
<property name="text">
<string>Single Point at</string>
</property>
<attribute name="buttonGroup">
<string notr="true">groupType</string>
</attribute>
</widget>
</item>
<item>
<widget class="SIUnitEdit" name="frequency"/>
</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>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,1">
<item>
<widget class="QRadioButton" name="array">
<property name="text">
<string>Array of </string>
</property>
<attribute name="buttonGroup">
<string notr="true">groupType</string>
</attribute>
</widget>
</item>
<item>
<widget class="QSpinBox" name="numPoints">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>64</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>points from</string>
</property>
</widget>
</item>
<item>
<widget class="SIUnitEdit" name="startFreq"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item>
<widget class="SIUnitEdit" name="stopFreq"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="deleteExisting">
<property name="text">
<string>Delete already existing points</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="groupType"/>
</buttongroups>
</ui>

View File

@ -3,12 +3,17 @@
#include "mode.h"
#include "unit.h"
#include <QDebug>
#include "ui_addamplitudepointsdialog.h"
#include <QMessageBox>
using namespace std;
AmplitudeCalDialog::AmplitudeCalDialog(Device *dev, QWidget *parent) :
QDialog(parent),
ui(new Ui::AmplitudeCalDialog),
dev(dev),
model(this)
model(this),
mode(CalibrationMode::BothPorts)
{
activeMode = Mode::getActiveMode();
activeMode->deactivate();
@ -20,9 +25,40 @@ AmplitudeCalDialog::AmplitudeCalDialog(Device *dev, QWidget *parent) :
ui->view->setColumnWidth(AmplitudeModel::ColIndexPort1, 150);
ui->view->setColumnWidth(AmplitudeModel::ColIndexPort2, 150);
connect(dev, &Device::AmplitudeCorrectionPointReceived, this, &AmplitudeCalDialog::ReceivedPoint);
connect(ui->load, &QPushButton::clicked, this, &AmplitudeCalDialog::LoadFromDevice);
connect(ui->add, &QPushButton::clicked, this, &AmplitudeCalDialog::AddPoint);
connect(ui->remove, &QPushButton::clicked, this, &AmplitudeCalDialog::RemovePoint);
connect(ui->load, &QPushButton::clicked, [=](){
if(ConfirmActionIfEdited()) {
LoadFromDevice();
}
});
connect(ui->save, &QPushButton::clicked, this, &AmplitudeCalDialog::SaveToDevice);
connect(ui->add, &QPushButton::clicked, this, &AmplitudeCalDialog::AddPointDialog);
connect(ui->remove, &QPushButton::clicked, [=](){
unsigned int row = ui->view->currentIndex().row();
if(row < points.size()) {
RemovePoint(row);
}
});
auto selectionModel = ui->view->selectionModel();
connect(selectionModel, &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex&) {
if(current.isValid() && (current.column() == model.ColIndexPort1 || current.column() == model.ColIndexPort2)) {
SelectedPoint(points[current.row()].frequency, current.column() == model.ColIndexPort2);
} else {
// Invalid selection
SelectedPoint(0, false);
}
});
connect(ui->modeBoth, &QPushButton::pressed, [=](){
mode = CalibrationMode::BothPorts;
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
});
connect(ui->modePort1, &QPushButton::pressed, [=](){
mode = CalibrationMode::OnlyPort1;
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
});
connect(ui->modePort2, &QPushButton::pressed, [=](){
mode = CalibrationMode::OnlyPort2;
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
});
}
AmplitudeCalDialog::~AmplitudeCalDialog()
@ -33,8 +69,9 @@ AmplitudeCalDialog::~AmplitudeCalDialog()
void AmplitudeCalDialog::reject()
{
// TODO check for unsaved data
if(ConfirmActionIfEdited()) {
delete this;
}
}
std::vector<AmplitudeCalDialog::CorrectionPoint> AmplitudeCalDialog::getPoints() const
@ -49,9 +86,24 @@ void AmplitudeCalDialog::setAmplitude(double amplitude, unsigned int point, bool
}
if(port2) {
points[point].amplitudePort2 = amplitude;
points[point].port2set = true;
} else {
points[point].amplitudePort1 = amplitude;
points[point].port1set = true;
}
edited = true;
AmplitudeChanged(points[point], port2);
if(mode == CalibrationMode::OnlyPort1 && !port2) {
// apply result from port 1 to port 2 as well
points[point].correctionPort2 = points[point].correctionPort1;
points[point].port2set = points[point].port1set;
} else if(mode == CalibrationMode::OnlyPort2 && port2) {
// apply result from port 2 to port 1 as well
points[point].correctionPort1 = points[point].correctionPort2;
points[point].port1set = points[point].port2set;
}
model.dataChanged(model.index(point, model.ColIndexCorrectionFactors), model.index(point, model.ColIndexPort2));
UpdateSaveButton();
}
void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
@ -61,7 +113,10 @@ void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
c.frequency = p.freq * 10.0;
c.correctionPort1 = p.port1;
c.correctionPort2 = p.port2;
c.port1set = false;
c.port2set = false;
model.beginInsertRows(QModelIndex(), points.size(), points.size());
UpdateAmplitude(c);
points.push_back(c);
model.endInsertRows();
emit pointsUpdated();
@ -69,15 +124,17 @@ void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
void AmplitudeCalDialog::LoadFromDevice()
{
model.beginResetModel();
points.clear();
model.endResetModel();
dev->SetIdle();
RemoveAllPoints();
qDebug() << "Asking for amplitude calibration";
dev->SendCommandWithoutPayload(requestCommand());
edited = false;
ui->save->setEnabled(false);
}
void AmplitudeCalDialog::SaveToDevice()
{
dev->SetIdle();
for(unsigned int i=0;i<points.size();i++) {
auto p = points[i];
Protocol::PacketInfo info;
@ -89,21 +146,134 @@ void AmplitudeCalDialog::SaveToDevice()
info.amplitudePoint.pointNum = i;
dev->SendPacket(info);
}
edited = false;
ui->save->setEnabled(false);
}
void AmplitudeCalDialog::RemovePoint()
void AmplitudeCalDialog::RemovePoint(unsigned int i)
{
unsigned int row = ui->view->currentIndex().row();
if(row < points.size()) {
model.beginRemoveRows(QModelIndex(), row, row);
points.erase(points.begin() + row);
model.beginRemoveRows(QModelIndex(), i, i);
points.erase(points.begin() + i);
model.endRemoveRows();
edited = true;
UpdateSaveButton();
}
void AmplitudeCalDialog::RemoveAllPoints()
{
model.beginResetModel();
points.clear();
model.endResetModel();
edited = true;
ui->save->setEnabled(false);
UpdateSaveButton();
}
void AmplitudeCalDialog::AddPoint(double frequency)
{
if(points.size() >= Device::Info().limits_maxAmplitudePoints) {
qWarning() << "Unable to add amplitude point, already maximum limit (" << Device::Info().limits_maxAmplitudePoints << ")";
return;
}
// find position at which this frequency gets inserted
auto index = upper_bound(points.begin(), points.end(), frequency, [](double value, const CorrectionPoint& p){
return value < p.frequency;
});
model.beginInsertRows(QModelIndex(), index - points.begin(), index - points.begin());
CorrectionPoint newPoint;
newPoint.frequency = frequency;
newPoint.correctionPort1 = 0;
newPoint.correctionPort2 = 0;
newPoint.port1set = false;
newPoint.port2set = false;
emit newPointCreated(newPoint);
points.insert(index, newPoint);
model.endInsertRows();
edited = true;
UpdateSaveButton();
}
void AmplitudeCalDialog::AddPointDialog()
{
auto d = new QDialog();
auto ui = new Ui::AddAmplitudePointsDialog();
ui->setupUi(d);
ui->frequency->setUnit("Hz");
ui->frequency->setPrefixes(" kMG");
ui->startFreq->setUnit("Hz");
ui->startFreq->setPrefixes(" kMG");
ui->stopFreq->setUnit("Hz");
ui->stopFreq->setPrefixes(" kMG");
ui->frequency->setValue(1000000000.0);
ui->startFreq->setValue(Device::Info().limits_minFreq);
ui->stopFreq->setValue(Device::Info().limits_maxFreq);
connect(ui->singlePoint, &QRadioButton::toggled, [=](bool single) {
ui->stopFreq->setEnabled(!single);
ui->startFreq->setEnabled(!single);
ui->numPoints->setEnabled(!single);
ui->frequency->setEnabled(single);
});
ui->singlePoint->setChecked(true);
ui->frequency->setFocus();
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
// okay clicked, apply selected action
if(ui->deleteExisting->isChecked()) {
RemoveAllPoints();
}
if(ui->singlePoint->isChecked()) {
AddPoint(ui->frequency->value());
} else {
double freq_start = ui->startFreq->value();
double freq_stop = ui->stopFreq->value();
unsigned int points = ui->numPoints->value();
double freq_step = (freq_stop - freq_start) / (points - 1);
for(unsigned int i=0;i<points;i++) {
AddPoint(freq_start + i * freq_step);
}
}
});
connect(d, &QDialog::rejected, ui->buttonBox, &QDialogButtonBox::rejected);
connect(ui->buttonBox, &QDialogButtonBox::rejected, [=](){
// aborted, nothing to do
delete d;
});
d->show();
}
bool AmplitudeCalDialog::ConfirmActionIfEdited()
{
if(edited) {
auto reply = QMessageBox::question(this, "Confirm action", "Some points have been edited but not saved in the device yet. If you continue, all changes will be lost. Do you want to continue?",
QMessageBox::Yes|QMessageBox::No);
return reply == QMessageBox::Yes;
} else {
// not edited yet, nothing to confirm
return true;
}
}
void AmplitudeCalDialog::AddPoint()
void AmplitudeCalDialog::UpdateSaveButton()
{
bool enable = true;
if(points.size() == 0) {
// needs at least one point
enable = false;
}
for(auto p : points) {
if(!p.port1set || !p.port2set) {
// some points are not set yet
enable = false;
break;
}
}
ui->save->setEnabled(enable);
}
AmplitudeCalDialog::CalibrationMode AmplitudeCalDialog::getMode() const
{
return mode;
}
AmplitudeModel::AmplitudeModel(AmplitudeCalDialog *c) :
@ -134,10 +304,18 @@ QVariant AmplitudeModel::data(const QModelIndex &index, int role) const
return QString::number(p.correctionPort1) + ", " + QString::number(p.correctionPort2);
break;
case ColIndexPort1:
if (p.port1set) {
return Unit::ToString(p.amplitudePort1, "dbm", " ", 4);
} else {
return "No data";
}
break;
case ColIndexPort2:
if (p.port2set) {
return Unit::ToString(p.amplitudePort2, "dbm", " ", 4);
} else {
return "No data";
}
break;
}
}
@ -180,8 +358,16 @@ Qt::ItemFlags AmplitudeModel::flags(const QModelIndex &index) const
{
int flags = Qt::NoItemFlags;
switch(index.column()) {
case ColIndexPort1: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexPort2: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexPort1:
if(c->getMode() != AmplitudeCalDialog::CalibrationMode::OnlyPort2) {
flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
break;
case ColIndexPort2:
if(c->getMode() != AmplitudeCalDialog::CalibrationMode::OnlyPort1) {
flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
break;
}
return (Qt::ItemFlags) flags;
}

View File

@ -1,4 +1,4 @@
#ifndef AMPLITUDECALDIALOG_H
#ifndef AMPLITUDECALDIALOG_H
#define AMPLITUDECALDIALOG_H
#include <QDialog>
@ -48,30 +48,53 @@ public:
class CorrectionPoint {
public:
double frequency;
double correctionPort1;
double correctionPort2;
int16_t correctionPort1;
int16_t correctionPort2;
double amplitudePort1;
double amplitudePort2;
bool port1set;
bool port2set;
};
std::vector<CorrectionPoint> getPoints() const;
void setAmplitude(double amplitude, unsigned int point, bool port2);
enum class CalibrationMode {
BothPorts,
OnlyPort1,
OnlyPort2,
};
CalibrationMode getMode() const;
protected slots:
void ReceivedPoint(Protocol::AmplitudeCorrectionPoint p);
void LoadFromDevice();
void SaveToDevice();
void RemovePoint();
void AddPoint();
void RemovePoint(unsigned int i);
void RemoveAllPoints();
void AddPoint(double frequency);
void AddPointDialog();
signals:
void pointsUpdated();
void newPointCreated(CorrectionPoint& p);
protected:
bool ConfirmActionIfEdited();
void UpdateSaveButton();
virtual Protocol::PacketType requestCommand() = 0;
virtual Protocol::PacketType pointType() = 0;
// will get called whenever a new cell is selected (frequency=0 means invalid selection)
virtual void SelectedPoint(double frequency, bool port2) = 0;
// will get called whenever the amplitude is changed. Derived class is responsible for updating correction factor
virtual void AmplitudeChanged(CorrectionPoint& point, bool port2) = 0;
// called whenver the correction factor have been retrieved from the device and the amplitudes need to be updated
virtual void UpdateAmplitude(CorrectionPoint& point) = 0;
std::vector<CorrectionPoint> points;
Ui::AmplitudeCalDialog *ui;
Device *dev;
Mode *activeMode;
AmplitudeModel model;
bool edited;
CalibrationMode mode;
};
#endif // SOURCECALDIALOG_H

View File

@ -28,11 +28,11 @@
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="add">
<property name="text">
<string>Add Point</string>
<string>Add Point(s)</string>
</property>
<property name="icon">
<iconset theme="list-add" resource="../icons.qrc">
@ -57,7 +57,8 @@
<string>Help</string>
</property>
<property name="icon">
<iconset theme="help"/>
<iconset theme="help">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
@ -74,6 +75,9 @@
</item>
<item>
<widget class="QPushButton" name="save">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save to Device</string>
</property>
@ -83,6 +87,101 @@
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Calibration Mode</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="modeBoth">
<property name="text">
<string>Both Ports</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">groupMode</string>
</attribute>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Best accuracy</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="modePort1">
<property name="text">
<string>Only Port 1</string>
</property>
<attribute name="buttonGroup">
<string notr="true">groupMode</string>
</attribute>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Use same correction for port 2</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="modePort2">
<property name="text">
<string>Only Port 2</string>
</property>
<attribute name="buttonGroup">
<string notr="true">groupMode</string>
</attribute>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Use same correction for port 1</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@ -104,4 +203,7 @@
<include location="../icons.qrc"/>
</resources>
<connections/>
<buttongroups>
<buttongroup name="groupMode"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,58 @@
#include "receivercaldialog.h"
ReceiverCalDialog::ReceiverCalDialog(Device *dev)
: AmplitudeCalDialog(dev)
{
setWindowTitle("Receiver Calibration Dialog");
LoadFromDevice();
connect(dev, &Device::SpectrumResultReceived, [=](Protocol::SpectrumAnalyzerResult res) {
if(res.pointNum == 1) {
// store result in center of sweep of 3 points
port1_result = 20*log10(res.port1);
port2_result = 20*log10(res.port2);
}
});
}
void ReceiverCalDialog::SelectedPoint(double frequency, bool port2)
{
if(frequency > 0) {
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 = frequency + 1.0;
p.spectrumSettings.f_start = frequency - 1.0;
p.spectrumSettings.pointNum = 3;
p.spectrumSettings.Detector = 0;
p.spectrumSettings.SignalID = 1;
p.spectrumSettings.WindowType = 3;
p.spectrumSettings.applyReceiverCorrection = 0;
dev->SendPacket(p);
} else {
// invalid frequency, disable
dev->SetIdle();
}
}
void ReceiverCalDialog::AmplitudeChanged(AmplitudeCalDialog::CorrectionPoint &point, bool port2)
{
auto *factor = port2 ? &point.correctionPort2 : &point.correctionPort1;
const auto *amplitude = port2 ? &point.amplitudePort2 : &point.amplitudePort1;
const auto *measured = port2 ? &port2_result : &port1_result;
// calculate correction factor by comparing expected with measured amplitude
*factor = (*measured - *amplitude) * 100.0;
}
void ReceiverCalDialog::UpdateAmplitude(AmplitudeCalDialog::CorrectionPoint &point)
{
// This point was just received from the device, it is not possible to know the actual amplitude because the
// applied power level during the calibration is not saved (only the correction value). This is not a problem
// because the correction value is still valid but the missing values look weird in the GUI
// TODO change this?
point.amplitudePort1 = std::numeric_limits<double>::quiet_NaN();
point.amplitudePort2 = std::numeric_limits<double>::quiet_NaN();
point.port1set = true;
point.port2set = true;
}

View File

@ -0,0 +1,22 @@
#ifndef RECEIVERCALDIALOG_H
#define RECEIVERCALDIALOG_H
#include "amplitudecaldialog.h"
class ReceiverCalDialog : public AmplitudeCalDialog
{
Q_OBJECT
public:
ReceiverCalDialog(Device *dev);
protected:
Protocol::PacketType requestCommand() override { return Protocol::PacketType::RequestReceiverCal; }
Protocol::PacketType pointType() override { return Protocol::PacketType::ReceiverCalPoint; }
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;
double port1_result, port2_result; // raw (uncorrected) measurements from device
};
#endif // RECEIVERCALDIALOG_H

View File

@ -1,8 +1,47 @@
#include "sourcecaldialog.h"
#include <QDebug>
SourceCalDialog::SourceCalDialog(Device *dev)
: AmplitudeCalDialog(dev)
{
setWindowTitle("Source Calibration Dialog");
LoadFromDevice();
}
void SourceCalDialog::SelectedPoint(double frequency, bool port2)
{
Protocol::PacketInfo p;
p.type = Protocol::PacketType::Generator;
p.generator.frequency = frequency;
p.generator.cdbm_level = excitationAmplitude * 100.0;
if(frequency > 0) {
if(port2) {
p.generator.activePort = 2;
} else {
p.generator.activePort = 1;
}
} else {
// invalid frequency, disable both ports
p.generator.activePort = 0;
}
p.generator.applyAmplitudeCorrection = 0;
dev->SendPacket(p);
}
void SourceCalDialog::AmplitudeChanged(AmplitudeCalDialog::CorrectionPoint &point, bool port2)
{
auto *factor = port2 ? &point.correctionPort2 : &point.correctionPort1;
const auto *amplitude = port2 ? &point.amplitudePort2 : &point.amplitudePort1;
// calculate correction factor by comparing expected with measured amplitude
*factor = (excitationAmplitude - *amplitude) * 100.0;
}
void SourceCalDialog::UpdateAmplitude(AmplitudeCalDialog::CorrectionPoint &point)
{
point.amplitudePort1 = excitationAmplitude - (double) point.correctionPort1 / 100.0;
point.amplitudePort2 = excitationAmplitude - (double) point.correctionPort2 / 100.0;
point.port1set = true;
point.port2set = true;
}

View File

@ -12,6 +12,11 @@ public:
protected:
Protocol::PacketType requestCommand() override { return Protocol::PacketType::RequestSourceCal; }
Protocol::PacketType pointType() override { return Protocol::PacketType::SourceCalPoint; }
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

View File

@ -136,6 +136,7 @@ static constexpr Protocol::DeviceInfo defaultInfo = {
.limits_cdbm_max = 1000,
.limits_minRBW = 1,
.limits_maxRBW = 1000000,
.limits_maxAmplitudePoints = 255,
};
Protocol::DeviceInfo Device::lastInfo = defaultInfo;

View File

@ -53,6 +53,7 @@ Protocol::GeneratorSettings SignalgeneratorWidget::getDeviceStatus()
} else {
s.activePort = 0;
}
s.applyAmplitudeCorrection = 1;
return s;
}

View File

@ -229,9 +229,6 @@ using namespace std;
void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d)
{
// TODO level adjustment in device
d.port1 /= 126500000.0;
d.port2 /= 126500000.0;
d = average.process(d);
traceModel.addSAData(d, settings);
emit dataChanged();
@ -248,6 +245,7 @@ void SpectrumAnalyzer::SettingsChanged()
} else {
settings.pointNum = settings.f_stop - settings.f_start + 1;
}
settings.applyReceiverCorrection = 1;
auto pref = Preferences::getInstance();
if(pref.Acquisition.useDFTinSAmode && settings.RBW <= pref.Acquisition.RBWLimitForDFT) {

View File

@ -45,6 +45,7 @@
#include "Generator/generator.h"
#include "SpectrumAnalyzer/spectrumanalyzer.h"
#include "Calibration/sourcecaldialog.h"
#include "Calibration/receivercaldialog.h"
using namespace std;
@ -102,6 +103,7 @@ AppWindow::AppWindow(QWidget *parent)
connect(ui->actionManual_Control, &QAction::triggered, this, &AppWindow::StartManualControl);
connect(ui->actionFirmware_Update, &QAction::triggered, this, &AppWindow::StartFirmwareUpdateDialog);
connect(ui->actionSource_Calibration, &QAction::triggered, this, &AppWindow::SourceCalibrationDialog);
connect(ui->actionReceiver_Calibration, &QAction::triggered, this, &AppWindow::ReceiverCalibrationDialog);
connect(ui->actionPreferences, &QAction::triggered, [=](){
Preferences::getInstance().edit();
// settings might have changed, update necessary stuff
@ -187,6 +189,7 @@ void AppWindow::ConnectToDevice(QString serial)
ui->actionManual_Control->setEnabled(true);
ui->actionFirmware_Update->setEnabled(true);
ui->actionSource_Calibration->setEnabled(true);
ui->actionReceiver_Calibration->setEnabled(true);
Mode::getActiveMode()->initializeDevice();
UpdateReference();
@ -214,6 +217,7 @@ void AppWindow::DisconnectDevice()
ui->actionManual_Control->setEnabled(false);
ui->actionFirmware_Update->setEnabled(false);
ui->actionSource_Calibration->setEnabled(false);
ui->actionReceiver_Calibration->setEnabled(false);
for(auto a : deviceActionGroup->actions()) {
a->setChecked(false);
}
@ -362,6 +366,12 @@ void AppWindow::SourceCalibrationDialog()
d->exec();
}
void AppWindow::ReceiverCalibrationDialog()
{
auto d = new ReceiverCalDialog(device);
d->exec();
}
Device *AppWindow::getDevice() const
{
return device;

View File

@ -45,6 +45,7 @@ private slots:
void StartFirmwareUpdateDialog();
void DeviceNeedsUpdate(int reported, int expected);
void SourceCalibrationDialog();
void ReceiverCalibrationDialog();
private:
void DeviceConnectionLost();
void CreateToolbars();

View File

@ -50,6 +50,7 @@
<addaction name="actionFirmware_Update"/>
<addaction name="separator"/>
<addaction name="actionSource_Calibration"/>
<addaction name="actionReceiver_Calibration"/>
</widget>
<widget class="QMenu" name="menuWindow">
<property name="title">
@ -161,6 +162,14 @@
<string>Source Calibration</string>
</property>
</action>
<action name="actionReceiver_Calibration">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Receiver Calibration</string>
</property>
</action>
</widget>
<resources>
<include location="icons.qrc"/>

View File

@ -44,7 +44,12 @@ bool AmplitudeCal::Save() {
if(!HWHAL::flash.eraseRange(flash_address, flash_size)) {
return false;
}
return HWHAL::flash.write(flash_address, sizeof(cal), &cal);
uint32_t write_size = sizeof(cal);
if(write_size % Flash::PageSize != 0) {
// round up to next page
write_size += Flash::PageSize - write_size % Flash::PageSize;
}
return HWHAL::flash.write(flash_address, write_size, &cal);
}
void AmplitudeCal::SetDefault() {
@ -78,9 +83,9 @@ static AmplitudeCal::Correction InterpolateCorrection(const CorrectionTable& tab
ret.port2 = table.port2Correction[table.usedPoints - 1];
} else {
// frequency is between i and i-1, interpolate
float alpha = (freq - table.freq[i - 1]) / (table.freq[i] - table.freq[i - 1]);
ret.port1 = table.port1Correction[i - 1] * (1 - alpha) + table.port1Correction[i] * alpha;
ret.port2 = table.port2Correction[i - 1] * (1 - alpha) + table.port2Correction[i] * alpha;
float alpha = (float) (freq - table.freq[i - 1]) / (table.freq[i] - table.freq[i - 1]);
ret.port1 = table.port1Correction[i - 1] * (1.0f - alpha) + table.port1Correction[i] * alpha;
ret.port2 = table.port2Correction[i - 1] * (1.0f - alpha) + table.port2Correction[i] * alpha;
}
return ret;
}
@ -116,6 +121,10 @@ void AmplitudeCal::SendReceiver() {
}
static void addPoint(CorrectionTable& table, const Protocol::AmplitudeCorrectionPoint& p) {
if(p.pointNum >= AmplitudeCal::maxPoints) {
// ignore out-of-bounds point
return;
}
table.freq[p.pointNum] = p.freq;
table.port1Correction[p.pointNum] = p.port1;
table.port2Correction[p.pointNum] = p.port2;

View File

@ -223,7 +223,8 @@ static Protocol::GeneratorSettings DecodeGeneratorSettings(uint8_t *buf) {
Decoder e(buf);
e.get<uint64_t>(d.frequency);
e.get<int16_t>(d.cdbm_level);
e.get<uint8_t>(d.activePort);
d.activePort = e.getBits(2);
d.applyAmplitudeCorrection = e.getBits(1);
return d;
}
static int16_t EncodeGeneratorSettings(Protocol::GeneratorSettings d, uint8_t *buf,
@ -231,7 +232,8 @@ static int16_t EncodeGeneratorSettings(Protocol::GeneratorSettings d, uint8_t *b
Encoder e(buf, bufSize);
e.add<uint64_t>(d.frequency);
e.add<int16_t>(d.cdbm_level);
e.add<uint8_t>(d.activePort);
e.addBits(d.activePort, 2);
e.addBits(d.applyAmplitudeCorrection, 1);
return e.getSize();
}
@ -261,6 +263,7 @@ static Protocol::DeviceInfo DecodeDeviceInfo(uint8_t *buf) {
e.get(d.limits_cdbm_max);
e.get(d.limits_minRBW);
e.get(d.limits_maxRBW);
e.get(d.limits_maxAmplitudePoints);
return d;
}
static int16_t EncodeDeviceInfo(Protocol::DeviceInfo d, uint8_t *buf,
@ -290,6 +293,7 @@ static int16_t EncodeDeviceInfo(Protocol::DeviceInfo d, uint8_t *buf,
e.add(d.limits_cdbm_max);
e.add(d.limits_minRBW);
e.add(d.limits_maxRBW);
e.add(d.limits_maxAmplitudePoints);
return e.getSize();
}
@ -402,6 +406,7 @@ static Protocol::SpectrumAnalyzerSettings DecodeSpectrumAnalyzerSettings(uint8_t
d.SignalID = e.getBits(1);
d.Detector = e.getBits(3);
d.UseDFT = e.getBits(1);
d.applyReceiverCorrection = e.getBits(1);
return d;
}
static int16_t EncodeSpectrumAnalyzerSettings(Protocol::SpectrumAnalyzerSettings d, uint8_t *buf,
@ -415,6 +420,7 @@ static int16_t EncodeSpectrumAnalyzerSettings(Protocol::SpectrumAnalyzerSettings
e.addBits(d.SignalID, 1);
e.addBits(d.Detector, 3);
e.addBits(d.UseDFT, 1);
e.addBits(d.applyReceiverCorrection, 1);
return e.getSize();
}

View File

@ -4,7 +4,7 @@
namespace Protocol {
static constexpr uint16_t Version = 1;
static constexpr uint16_t Version = 2;
// When changing/adding/removing variables from these structs also adjust the decode/encode functions in Protocol.cpp
@ -37,7 +37,8 @@ using ReferenceSettings = struct _referenceSettings {
using GeneratorSettings = struct _generatorSettings {
uint64_t frequency;
int16_t cdbm_level;
uint8_t activePort;
uint8_t activePort :2;
uint8_t applyAmplitudeCorrection :1;
};
using DeviceInfo = struct _deviceInfo {
@ -64,6 +65,7 @@ using DeviceInfo = struct _deviceInfo {
int16_t limits_cdbm_max;
uint32_t limits_minRBW;
uint32_t limits_maxRBW;
uint8_t limits_maxAmplitudePoints;
};
using ManualStatus = struct _manualstatus {
@ -119,6 +121,7 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings {
uint8_t SignalID :1;
uint8_t Detector :3;
uint8_t UseDFT :1;
uint8_t applyReceiverCorrection :1;
};
using SpectrumAnalyzerResult = struct _spectrumAnalyzerResult {

View File

@ -30,7 +30,7 @@ void Flash::read(uint32_t address, uint16_t length, void *dest) {
}
bool Flash::write(uint32_t address, uint16_t length, void *src) {
if((address & 0xFF) != 0 || length%256 != 0) {
if(address % PageSize != 0 || length%PageSize != 0) {
// only writes to complete pages allowed
LOG_ERR("Invalid write address/size: %lu/%u", address, length);
return false;
@ -46,7 +46,7 @@ bool Flash::write(uint32_t address, uint16_t length, void *src) {
(uint8_t) (address >> 8) & 0xFF,
(uint8_t) (address & 0xFF),
};
// issue read command
// issue write command
HAL_SPI_Transmit(spi, cmd, 4, 100);
// write data
HAL_SPI_Transmit(spi, (uint8_t*) src, 256, 1000);

View File

@ -28,11 +28,12 @@ public:
const SPI_HandleTypeDef* const getSpi() const {
return spi;
}
private:
static constexpr uint32_t PageSize = 256;
static constexpr uint32_t SectorSize = 4096;
static constexpr uint32_t Block32Size = 32768;
static constexpr uint32_t Block64Size = 65536;
private:
void CS(bool high) {
if(high) {
CS_gpio->BSRR = CS_pin;

View File

@ -3,6 +3,7 @@
#include "Hardware.hpp"
#include "max2871.hpp"
#include "Si5351C.hpp"
#include "AmplitudeCal.hpp"
static constexpr uint32_t BandSwitchFrequency = 25000000;
@ -25,6 +26,25 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
m.RefEN = 0;
m.Samples = 131072;
m.WindowType = (int) FPGA::Window::None;
switch(g.activePort) {
case 1:
m.AmplifierEN = 1;
m.PortSwitch = 0;
break;
case 2:
m.AmplifierEN = 1;
m.PortSwitch = 1;
break;
}
if (g.applyAmplitudeCorrection) {
auto correction = AmplitudeCal::SourceCorrection(g.frequency);
if (g.activePort == 1) {
g.cdbm_level += correction.port1;
} else {
g.cdbm_level += correction.port2;
}
}
// Select correct source
if(g.frequency < BandSwitchFrequency) {
m.SourceLowEN = 1;
@ -35,6 +55,16 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
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.SourceHighPower = (int) MAX2871::Power::n4dbm;
} else {
m.SourceLowEN = 0;
m.SourceLowFrequency = BandSwitchFrequency;
@ -51,32 +81,25 @@ void Generator::Setup(Protocol::GeneratorSettings g) {
m.SourceHighLowpass = (int) FPGA::LowpassFilter::None;
}
m.SourceHighband = true;
}
switch(g.activePort) {
case 1:
m.AmplifierEN = 1;
m.PortSwitch = 0;
break;
case 2:
m.AmplifierEN = 1;
m.PortSwitch = 1;
break;
}
// Set level (not very accurate)
if(g.cdbm_level > -1000) {
// use higher source power (approx 0dbm with no attenuation)
m.SourceHighPower = (int) MAX2871::Power::p5dbm;
m.SourceLowPower = (int) Si5351C::DriveStrength::mA8;
} else {
// use lower source power (approx -10dbm with no attenuation)
if(g.cdbm_level <= HW::HighBandMinPower) {
// can use the low power setting
m.SourceHighPower = (int) MAX2871::Power::n4dbm;
m.SourceLowPower = (int) Si5351C::DriveStrength::mA4;
g.cdbm_level += 1000;
g.cdbm_level -= HW::HighBandMinPower;
} else {
// needs the high power setting
m.SourceHighPower = (int) MAX2871::Power::p5dbm;
g.cdbm_level -= HW::HighBandMaxPower;
}
m.SourceLowPower = (int) MAX2871::Power::n4dbm;
}
// calculate required attenuation
uint16_t attval = -g.cdbm_level / 25;
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;
Manual::Setup(m);

View File

@ -3,6 +3,7 @@
#include <cstdint>
#include "Protocol.hpp"
#include "FPGA/FPGA.hpp"
#include "AmplitudeCal.hpp"
#define USE_DEBUG_PINS
@ -38,6 +39,12 @@ static_assert(ADCprescaler * ADCSamplerate == FPGA::Clockrate, "ADCSamplerate ca
static constexpr uint16_t DFTphaseInc = 4096 * IF2 / ADCSamplerate;
static_assert(DFTphaseInc * ADCSamplerate == 4096 * IF2, "DFT can not be computed for 2.IF");
// approximate output power at low frequencies with different source strength settings (attenuator = 0) in cdbm
static constexpr int16_t LowBandMinPower = -1350;
static constexpr int16_t LowBandMaxPower = -190;
static constexpr int16_t HighBandMinPower = -1060;
static constexpr int16_t HighBandMaxPower = -160;
static constexpr Protocol::DeviceInfo Info = {
.ProtocolVersion = Protocol::Version,
.FW_major = FW_MAJOR,
@ -62,6 +69,7 @@ static constexpr Protocol::DeviceInfo Info = {
.limits_cdbm_max = 0,
.limits_minRBW = (uint32_t) (ADCSamplerate * 2.23f / MaxSamples),
.limits_maxRBW = (uint32_t) (ADCSamplerate * 2.23f / MinSamples),
.limits_maxAmplitudePoints = AmplitudeCal::maxPoints,
};
enum class Mode {

View File

@ -219,8 +219,8 @@ bool SA::MeasurementDone(const FPGA::SamplingResult &result) {
port1 = dft.P1;
port2 = dft.P2;
} else {
port1 = abs(std::complex<float>(result.P1I, result.P1Q));
port2 = abs(std::complex<float>(result.P2I, result.P2Q));
port1 = fabs(std::complex<float>(result.P1I, result.P1Q));
port2 = fabs(std::complex<float>(result.P2I, result.P2Q));
}
port1 /= sampleNum;
port2 /= sampleNum;
@ -315,8 +315,16 @@ void SA::Work() {
// Send result to application
p.type = Protocol::PacketType::SpectrumAnalyzerResult;
// measurements are already up to date, fill remaining fields
p.spectrumResult.pointNum = binIndex;
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;
if (s.applyReceiverCorrection) {
auto correction = AmplitudeCal::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);
}
p.spectrumResult.pointNum = binIndex;
Communication::Send(p);
}
}