diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index 763208d..c011c19 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -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 \ diff --git a/Software/PC_Application/Calibration/addamplitudepointsdialog.ui b/Software/PC_Application/Calibration/addamplitudepointsdialog.ui new file mode 100644 index 0000000..67b6521 --- /dev/null +++ b/Software/PC_Application/Calibration/addamplitudepointsdialog.ui @@ -0,0 +1,138 @@ + + + AddAmplitudePointsDialog + + + + 0 + 0 + 564 + 139 + + + + Add points + + + true + + + + + + + + Single Point at + + + groupType + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Array of + + + groupType + + + + + + + 1 + + + 64 + + + 10 + + + + + + + points from + + + + + + + + + + to + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Delete already existing points + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+
+ + + + + +
diff --git a/Software/PC_Application/Calibration/amplitudecaldialog.cpp b/Software/PC_Application/Calibration/amplitudecaldialog.cpp index 3c0119e..6cd4182 100644 --- a/Software/PC_Application/Calibration/amplitudecaldialog.cpp +++ b/Software/PC_Application/Calibration/amplitudecaldialog.cpp @@ -3,12 +3,17 @@ #include "mode.h" #include "unit.h" #include +#include "ui_addamplitudepointsdialog.h" +#include + +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 ¤t, 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 - delete this; + if(ConfirmActionIfEdited()) { + delete this; + } } std::vector 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;iSendPacket(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.endInsertRows(); + 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;ibuttonBox, &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: - return Unit::ToString(p.amplitudePort1, "dbm", " ", 4); + if (p.port1set) { + return Unit::ToString(p.amplitudePort1, "dbm", " ", 4); + } else { + return "No data"; + } break; case ColIndexPort2: - return Unit::ToString(p.amplitudePort2, "dbm", " ", 4); + 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; } diff --git a/Software/PC_Application/Calibration/amplitudecaldialog.h b/Software/PC_Application/Calibration/amplitudecaldialog.h index 9736fa3..d28d444 100644 --- a/Software/PC_Application/Calibration/amplitudecaldialog.h +++ b/Software/PC_Application/Calibration/amplitudecaldialog.h @@ -1,4 +1,4 @@ -#ifndef AMPLITUDECALDIALOG_H +#ifndef AMPLITUDECALDIALOG_H #define AMPLITUDECALDIALOG_H #include @@ -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 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 points; Ui::AmplitudeCalDialog *ui; Device *dev; Mode *activeMode; AmplitudeModel model; + bool edited; + CalibrationMode mode; }; #endif // SOURCECALDIALOG_H diff --git a/Software/PC_Application/Calibration/amplitudecaldialog.ui b/Software/PC_Application/Calibration/amplitudecaldialog.ui index 53a1e4b..baf0099 100644 --- a/Software/PC_Application/Calibration/amplitudecaldialog.ui +++ b/Software/PC_Application/Calibration/amplitudecaldialog.ui @@ -28,11 +28,11 @@ - + - Add Point + Add Point(s) @@ -57,7 +57,8 @@ Help - + + .. @@ -74,6 +75,9 @@ + + false + Save to Device @@ -83,6 +87,101 @@ + + + + Calibration Mode + + + + + + Both Ports + + + true + + + groupMode + + + + + + + + 8 + + + + Best accuracy + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + + + + Only Port 1 + + + groupMode + + + + + + + + 8 + + + + Use same correction for port 2 + + + + + + + Qt::Horizontal + + + + + + + Only Port 2 + + + groupMode + + + + + + + + 8 + + + + Use same correction for port 1 + + + + + + @@ -104,4 +203,7 @@ + + + diff --git a/Software/PC_Application/Calibration/receivercaldialog.cpp b/Software/PC_Application/Calibration/receivercaldialog.cpp new file mode 100644 index 0000000..fff53d3 --- /dev/null +++ b/Software/PC_Application/Calibration/receivercaldialog.cpp @@ -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::quiet_NaN(); + point.amplitudePort2 = std::numeric_limits::quiet_NaN(); + point.port1set = true; + point.port2set = true; +} diff --git a/Software/PC_Application/Calibration/receivercaldialog.h b/Software/PC_Application/Calibration/receivercaldialog.h new file mode 100644 index 0000000..b10ffbc --- /dev/null +++ b/Software/PC_Application/Calibration/receivercaldialog.h @@ -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 diff --git a/Software/PC_Application/Calibration/sourcecaldialog.cpp b/Software/PC_Application/Calibration/sourcecaldialog.cpp index 1247bfd..9d047b1 100644 --- a/Software/PC_Application/Calibration/sourcecaldialog.cpp +++ b/Software/PC_Application/Calibration/sourcecaldialog.cpp @@ -1,8 +1,47 @@ #include "sourcecaldialog.h" +#include + 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; +} diff --git a/Software/PC_Application/Calibration/sourcecaldialog.h b/Software/PC_Application/Calibration/sourcecaldialog.h index cc6813f..15de04d 100644 --- a/Software/PC_Application/Calibration/sourcecaldialog.h +++ b/Software/PC_Application/Calibration/sourcecaldialog.h @@ -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 diff --git a/Software/PC_Application/Device/device.cpp b/Software/PC_Application/Device/device.cpp index 5e63852..c5fde83 100644 --- a/Software/PC_Application/Device/device.cpp +++ b/Software/PC_Application/Device/device.cpp @@ -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; diff --git a/Software/PC_Application/Generator/signalgenwidget.cpp b/Software/PC_Application/Generator/signalgenwidget.cpp index 68735eb..603bf86 100644 --- a/Software/PC_Application/Generator/signalgenwidget.cpp +++ b/Software/PC_Application/Generator/signalgenwidget.cpp @@ -53,6 +53,7 @@ Protocol::GeneratorSettings SignalgeneratorWidget::getDeviceStatus() } else { s.activePort = 0; } + s.applyAmplitudeCorrection = 1; return s; } diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index a20ab65..cc5ed83 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -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) { diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index a007bbb..ec39160 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -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; diff --git a/Software/PC_Application/appwindow.h b/Software/PC_Application/appwindow.h index 1d32ee4..d6b9ef3 100644 --- a/Software/PC_Application/appwindow.h +++ b/Software/PC_Application/appwindow.h @@ -45,6 +45,7 @@ private slots: void StartFirmwareUpdateDialog(); void DeviceNeedsUpdate(int reported, int expected); void SourceCalibrationDialog(); + void ReceiverCalibrationDialog(); private: void DeviceConnectionLost(); void CreateToolbars(); diff --git a/Software/PC_Application/main.ui b/Software/PC_Application/main.ui index 2f50b04..91de82d 100644 --- a/Software/PC_Application/main.ui +++ b/Software/PC_Application/main.ui @@ -50,6 +50,7 @@ + @@ -161,6 +162,14 @@ Source Calibration + + + false + + + Receiver Calibration + + diff --git a/Software/VNA_embedded/Application/AmplitudeCal.cpp b/Software/VNA_embedded/Application/AmplitudeCal.cpp index a181805..c6984f7 100644 --- a/Software/VNA_embedded/Application/AmplitudeCal.cpp +++ b/Software/VNA_embedded/Application/AmplitudeCal.cpp @@ -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; diff --git a/Software/VNA_embedded/Application/Communication/Protocol.cpp b/Software/VNA_embedded/Application/Communication/Protocol.cpp index 0fc5bf7..8ad534a 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.cpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.cpp @@ -223,7 +223,8 @@ static Protocol::GeneratorSettings DecodeGeneratorSettings(uint8_t *buf) { Decoder e(buf); e.get(d.frequency); e.get(d.cdbm_level); - e.get(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(d.frequency); e.add(d.cdbm_level); - e.add(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(); } diff --git a/Software/VNA_embedded/Application/Communication/Protocol.hpp b/Software/VNA_embedded/Application/Communication/Protocol.hpp index 12618d8..adb3f1e 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.hpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.hpp @@ -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 { diff --git a/Software/VNA_embedded/Application/Drivers/Flash.cpp b/Software/VNA_embedded/Application/Drivers/Flash.cpp index ac9571f..a59a85d 100644 --- a/Software/VNA_embedded/Application/Drivers/Flash.cpp +++ b/Software/VNA_embedded/Application/Drivers/Flash.cpp @@ -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); diff --git a/Software/VNA_embedded/Application/Drivers/Flash.hpp b/Software/VNA_embedded/Application/Drivers/Flash.hpp index a011d15..d904290 100644 --- a/Software/VNA_embedded/Application/Drivers/Flash.hpp +++ b/Software/VNA_embedded/Application/Drivers/Flash.hpp @@ -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; diff --git a/Software/VNA_embedded/Application/Generator.cpp b/Software/VNA_embedded/Application/Generator.cpp index cedb9d8..1c03e64 100644 --- a/Software/VNA_embedded/Application/Generator.cpp +++ b/Software/VNA_embedded/Application/Generator.cpp @@ -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; + 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.SourceLowPower = (int) MAX2871::Power::n4dbm; } - 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) - m.SourceHighPower = (int) MAX2871::Power::n4dbm; - m.SourceLowPower = (int) Si5351C::DriveStrength::mA4; - g.cdbm_level += 1000; - } + // 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); diff --git a/Software/VNA_embedded/Application/Hardware.hpp b/Software/VNA_embedded/Application/Hardware.hpp index fca7f24..fdf2177 100644 --- a/Software/VNA_embedded/Application/Hardware.hpp +++ b/Software/VNA_embedded/Application/Hardware.hpp @@ -3,6 +3,7 @@ #include #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 { diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp index 0b63c89..cf7f5cc 100644 --- a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp @@ -219,8 +219,8 @@ bool SA::MeasurementDone(const FPGA::SamplingResult &result) { port1 = dft.P1; port2 = dft.P2; } else { - port1 = abs(std::complex(result.P1I, result.P1Q)); - port2 = abs(std::complex(result.P2I, result.P2Q)); + port1 = fabs(std::complex(result.P1I, result.P1Q)); + port2 = fabs(std::complex(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); } }