#include "librevnadriver.h" #include "manualcontroldialogV1.h" #include "manualcontroldialogvff.h" #include "deviceconfigurationdialogv1.h" #include "deviceconfigurationdialogvff.h" #include "firmwareupdatedialog.h" #include "frequencycaldialog.h" #include "sourcecaldialog.h" #include "receivercaldialog.h" #include "unit.h" #include "CustomWidgets/informationbox.h" #include "devicepacketlogview.h" #include "ui_librevnadriversettingswidget.h" using namespace std; class Reference { public: enum class TypeIn { Internal, External, Auto, None }; enum class OutFreq { MHZ10, MHZ100, Off, None }; static QString OutFreqToLabel(Reference::OutFreq t) { switch(t) { case OutFreq::MHZ10: return "10 MHz"; case OutFreq::MHZ100: return "100 MHz"; case OutFreq::Off: return "Off"; default: return "Invalid"; } } static QString OutFreqToKey(Reference::OutFreq f) { switch(f) { case OutFreq::MHZ10: return "10 MHz"; case OutFreq::MHZ100: return "100 MHz"; case OutFreq::Off: return "Off"; default: return "Invalid"; } } static Reference::OutFreq KeyToOutFreq(QString key) { for (auto r: Reference::getOutFrequencies()) { if(OutFreqToKey(r) == key|| OutFreqToLabel(r) == key) { return r; } } // not found return Reference::OutFreq::None; } static QString TypeToLabel(TypeIn t) { switch(t) { case TypeIn::Internal: return "Internal"; case TypeIn::External: return "External"; case TypeIn::Auto: return "Auto"; default: return "Invalid"; } } static const QString TypeToKey(TypeIn t) { switch(t) { case TypeIn::Internal: return "Int"; case TypeIn::External: return "Ext"; case TypeIn::Auto: return "Auto"; default: return "Invalid"; } } static TypeIn KeyToType(QString key) { for (auto r: Reference::getReferencesIn()) { if(TypeToKey(r) == key || TypeToLabel(r) == key) { return r; } } // not found return TypeIn::None; } static std::vector getReferencesIn() { return {TypeIn::Internal, TypeIn::External, TypeIn::Auto}; } static std::vector getOutFrequencies() { return {OutFreq::Off, OutFreq::MHZ10, OutFreq::MHZ100}; } }; LibreVNADriver::LibreVNADriver() { connected = false; skipOwnPacketHandling = false; SApoints = 0; hardwareVersion = 0; setSynchronization(Synchronization::Disabled, false); auto manual = new QAction("Manual Control"); connect(manual, &QAction::triggered, this, [=](){ QDialog *d = nullptr; switch(hardwareVersion) { case 1: d = new ManualControlDialogV1(*this); break; case 0xFF: d = new ManualControlDialogVFF(*this); break; } if(d) { d->show(); } }); specificActions.push_back(manual); auto config = new QAction("Configuration"); connect(config, &QAction::triggered, this, [=](){ QDialog *d = nullptr; switch(hardwareVersion) { case 1: d = new DeviceConfigurationDialogV1(*this); break; case 0xFF: d = new DeviceConfigurationDialogVFF(*this); break; } if(d) { d->show(); } }); specificActions.push_back(config); auto update = new QAction("Firmware Update"); connect(update, &QAction::triggered, this, [=](){ auto d = new FirmwareUpdateDialog(this); d->show(); }); specificActions.push_back(update); auto sep = new QAction(); sep->setSeparator(true); specificActions.push_back(sep); auto srccal = new QAction("Source Calibration"); connect(srccal, &QAction::triggered, this, [=](){ auto d = new SourceCalDialog(this); d->show(); }); specificActions.push_back(srccal); auto recvcal = new QAction("Receiver Calibration"); connect(recvcal, &QAction::triggered, this, [=](){ auto d = new ReceiverCalDialog(this); d->show(); }); specificActions.push_back(recvcal); auto freqcal = new QAction("Frequency Calibration"); connect(freqcal, &QAction::triggered, this, [=](){ auto d = new FrequencyCalDialog(this); d->show(); }); specificActions.push_back(freqcal); sep = new QAction(); sep->setSeparator(true); specificActions.push_back(sep); auto log = new QAction("View Packet Log"); connect(log, &QAction::triggered, this, [=](){ auto d = new DevicePacketLogView(); d->show(); }); specificActions.push_back(log); } std::set LibreVNADriver::getFlags() { std::set ret; switch(hardwareVersion) { case 1: if(lastStatus.V1.extRefInUse) { ret.insert(Flag::ExtRef); } if(!lastStatus.V1.source_locked || !lastStatus.V1.LO1_locked) { ret.insert(Flag::Unlocked); } if(lastStatus.V1.unlevel) { ret.insert(Flag::Unlevel); } if(lastStatus.V1.ADC_overload) { ret.insert(Flag::Overload); } break; case 0xFF: if(!lastStatus.VFF.source_locked || !lastStatus.VFF.LO_locked) { ret.insert(Flag::Unlocked); } if(lastStatus.VFF.unlevel) { ret.insert(Flag::Unlevel); } if(lastStatus.VFF.ADC_overload) { ret.insert(Flag::Overload); } break; } return ret; } QString LibreVNADriver::getStatus() { QString ret; ret.append("HW "); ret.append(info.hardware_version); ret.append(" FW "+info.firmware_version); switch (hardwareVersion) { case 1: ret.append(" Temps: "+QString::number(lastStatus.V1.temp_source)+"°C/"+QString::number(lastStatus.V1.temp_LO1)+"°C/"+QString::number(lastStatus.V1.temp_MCU)+"°C"); ret.append(" Reference:"); if(lastStatus.V1.extRefInUse) { ret.append("External"); } else { ret.append("Internal"); if(lastStatus.V1.extRefAvailable) { ret.append(" (External available)"); } } break; case 0xFF: ret.append(" MCU Temp: "+QString::number(lastStatus.VFF.temp_MCU)+"°C"); break; } return ret; } QWidget *LibreVNADriver::createSettingsWidget() { auto w = new QWidget; auto ui = new Ui::LibreVNADriverSettingsWidget; ui->setupUi(w); // Set initial values ui->CaptureRawReceiverValues->setChecked(captureRawReceiverValues); ui->UseHarmonicMixing->setChecked(harmonicMixing); ui->UseSignalID->setChecked(SASignalID); ui->SuppressPeaks->setChecked(VNASuppressInvalidPeaks); ui->AdjustPowerLevel->setChecked(VNAAdjustPowerLevel); ui->DFTlimitRBW->setEnabled(false); connect(ui->UseDFT, &QCheckBox::toggled, ui->DFTlimitRBW, &SIUnitEdit::setEnabled); ui->UseDFT->setChecked(SAUseDFT); ui->DFTlimitRBW->setUnit("Hz"); ui->DFTlimitRBW->setPrefixes(" kM"); ui->DFTlimitRBW->setPrecision(3); ui->DFTlimitRBW->setValue(SARBWLimitForDFT); connect(ui->UseHarmonicMixing, &QCheckBox::toggled, [=](bool enabled) { if(enabled) { InformationBox::ShowMessage("Harmonic Mixing", "When harmonic mixing is enabled, the frequency range of the VNA is (theoretically) extended up to 18GHz " "by using higher harmonics of the source signal as well as the 1.LO. The fundamental frequency is still present " "in the output signal and might disturb the measurement if the DUT is not linear. Performance above 6GHz is not " "specified and generally not very good. However, this mode might be useful if the signal of interest is just above " "6GHz (typically useful values up to 7-8GHz). Performance below 6GHz is not affected by this setting"); } }); // make connections to change the values connect(ui->CaptureRawReceiverValues, &QCheckBox::toggled, this, [=](){ captureRawReceiverValues = ui->CaptureRawReceiverValues->isChecked(); }); connect(ui->UseHarmonicMixing, &QCheckBox::toggled, this, [=](){ harmonicMixing = ui->UseHarmonicMixing->isChecked(); }); connect(ui->UseSignalID, &QCheckBox::toggled, this, [=](){ SASignalID = ui->UseSignalID->isChecked(); }); connect(ui->SuppressPeaks, &QCheckBox::toggled, this, [=](){ VNASuppressInvalidPeaks = ui->SuppressPeaks->isChecked(); }); connect(ui->AdjustPowerLevel, &QCheckBox::toggled, this, [=](){ VNAAdjustPowerLevel = ui->AdjustPowerLevel->isChecked(); }); connect(ui->UseDFT, &QCheckBox::toggled, this, [=](){ SAUseDFT = ui->UseDFT->isChecked(); }); connect(ui->DFTlimitRBW, &SIUnitEdit::valueChanged, this, [=](){ SARBWLimitForDFT = ui->DFTlimitRBW->value(); }); return w; } QStringList LibreVNADriver::availableVNAMeasurements() { QStringList ret; for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) { for(unsigned int j=1;j<=info.Limits.VNA.ports;j++) { ret.push_back("S"+QString::number(i)+QString::number(j)); } } if(captureRawReceiverValues) { for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) { for(unsigned int j=0;j cb) { if(!supports(Feature::VNA)) { return false; } if(s.excitedPorts.size() == 0) { return setIdle(cb); } // create port->stage mapping portStageMapping.clear(); for(unsigned int i=0;i cb) { if(!supports(Feature::SA)) { return false; } zerospan = s.freqStart == s.freqStop; Protocol::PacketInfo p = {}; p.type = Protocol::PacketType::SpectrumAnalyzerSettings; p.spectrumSettings.f_start = s.freqStart; p.spectrumSettings.f_stop = s.freqStop; constexpr unsigned int maxSApoints = 1001; if(s.freqStop - s.freqStart >= maxSApoints || s.freqStop - s.freqStart <= 0) { SApoints = maxSApoints; } else { SApoints = s.freqStop - s.freqStart + 1; } p.spectrumSettings.pointNum = SApoints; p.spectrumSettings.RBW = s.RBW; p.spectrumSettings.WindowType = (int) s.window; p.spectrumSettings.SignalID = SASignalID ? 1 : 0; p.spectrumSettings.Detector = (int) s.detector; p.spectrumSettings.UseDFT = 0; if(!s.trackingGenerator && SAUseDFT && s.RBW <= SARBWLimitForDFT) { p.spectrumSettings.UseDFT = 1; } p.spectrumSettings.applyReceiverCorrection = 1; p.spectrumSettings.trackingGeneratorOffset = s.trackingOffset; p.spectrumSettings.trackingPower = s.trackingPower * 100; p.spectrumSettings.trackingGenerator = s.trackingGenerator ? 1 : 0; p.spectrumSettings.trackingGeneratorPort = s.trackingPort - 1; p.spectrumSettings.syncMode = (int) sync; p.spectrumSettings.syncMaster = syncMaster ? 1 : 0; if(p.spectrumSettings.trackingGenerator && p.spectrumSettings.f_stop >= 25000000) { // Check point spacing. // The highband PLL used as the tracking generator is not able to reach every frequency exactly. This // could lead to sharp drops in the spectrum at certain frequencies. If the span is wide enough with // respect to the point number, it is ensured that every displayed point has at least one sample with // a reachable PLL frequency in it. Display a warning message if this is not the case with the current // settings. auto pointSpacing = (p.spectrumSettings.f_stop - p.spectrumSettings.f_start) / (p.spectrumSettings.pointNum - 1); // The frequency resolution of the PLL is frequency dependent (due to PLL divider). // This code assumes some knowledge of the actual hardware and probably should be moved // onto the device at some point double minSpacing = 25000; auto stop = p.spectrumSettings.f_stop; while(stop <= 3000000000) { minSpacing /= 2; stop *= 2; } if(pointSpacing < minSpacing) { auto requiredMinSpan = minSpacing * (p.spectrumSettings.pointNum - 1); auto message = QString() + "Due to PLL limitations, the tracking generator can not reach every frequency exactly. " "With your current span, this could result in the signal not being detected at some bands. A minimum" " span of " + Unit::ToString(requiredMinSpan, "Hz", " kMG") + " is recommended at this stop frequency."; InformationBox::ShowMessage("Warning", message, "TrackingGeneratorSpanTooSmallWarning"); } } return SendPacket(p, [=](TransmissionResult r){ if(cb) { cb(r == TransmissionResult::Ack); } }); } QStringList LibreVNADriver::availableSGPorts() { QStringList ret; for(unsigned int i=1;i cb) { Protocol::PacketInfo p; p.type = Protocol::PacketType::SetIdle; return SendPacket(p, [=](TransmissionResult res) { if(cb) { cb(res == TransmissionResult::Ack); } }); } QStringList LibreVNADriver::availableExtRefInSettings() { QStringList ret; if(hardwareVersion == 0x01) { for(auto r : Reference::getReferencesIn()) { ret.push_back(Reference::TypeToLabel(r)); } } return ret; } QStringList LibreVNADriver::availableExtRefOutSettings() { QStringList ret; if(hardwareVersion == 0x01) { for(auto r : Reference::getOutFrequencies()) { ret.push_back(Reference::OutFreqToLabel(r)); } } return ret; } bool LibreVNADriver::setExtRef(QString option_in, QString option_out) { auto refIn = Reference::KeyToType(option_in); if(refIn == Reference::TypeIn::None) { refIn = Reference::TypeIn::Internal; } auto refOut = Reference::KeyToOutFreq(option_out); if(refOut == Reference::OutFreq::None) { refOut = Reference::OutFreq::Off; } Protocol::PacketInfo p = {}; p.type = Protocol::PacketType::Reference; switch(refIn) { case Reference::TypeIn::Internal: case Reference::TypeIn::None: p.reference.UseExternalRef = 0; p.reference.AutomaticSwitch = 0; break; case Reference::TypeIn::Auto: p.reference.UseExternalRef = 0; p.reference.AutomaticSwitch = 1; break; case Reference::TypeIn::External: p.reference.UseExternalRef = 1; p.reference.AutomaticSwitch = 0; break; } switch(refOut) { case Reference::OutFreq::None: case Reference::OutFreq::Off: p.reference.ExtRefOuputFreq = 0; break; case Reference::OutFreq::MHZ10: p.reference.ExtRefOuputFreq = 10000000; break; case Reference::OutFreq::MHZ100: p.reference.ExtRefOuputFreq = 100000000; break; } return SendPacket(p); } void LibreVNADriver::registerTypes() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } void LibreVNADriver::setSynchronization(LibreVNADriver::Synchronization s, bool master) { sync = s; syncMaster = master; } void LibreVNADriver::handleReceivedPacket(const Protocol::PacketInfo &packet) { emit passOnReceivedPacket(packet); if(skipOwnPacketHandling) { return; } switch(packet.type) { case Protocol::PacketType::DeviceInfo: { // Check protocol version if(packet.info.ProtocolVersion != Protocol::Version) { auto ret = InformationBox::AskQuestion("Warning", "The device reports a different protocol" "version (" + QString::number(packet.info.ProtocolVersion) + ") than expected (" + QString::number(Protocol::Version) + ").\n" "A firmware update is strongly recommended. Do you want to update now?", false); if (ret) { auto d = new FirmwareUpdateDialog(this); d->show(); } } hardwareVersion = packet.info.hardware_version; info.firmware_version = QString::number(packet.info.FW_major)+"."+QString::number(packet.info.FW_minor)+"."+QString::number(packet.info.FW_patch); info.hardware_version = hardwareVersionToString(packet.info.hardware_version)+" Rev."+QString(packet.info.HW_Revision); info.supportedFeatures = { Feature::VNA, Feature::VNAFrequencySweep, Feature::VNALogSweep, Feature::VNAPowerSweep, Feature::VNAZeroSpan, Feature::Generator, Feature::SA, Feature::SATrackingGenerator, Feature::SATrackingOffset, Feature::ExtRefIn, Feature::ExtRefOut, }; info.Limits.VNA.ports = packet.info.num_ports; info.Limits.VNA.minFreq = packet.info.limits_minFreq; info.Limits.VNA.maxFreq = harmonicMixing ? packet.info.limits_maxFreqHarmonic : packet.info.limits_maxFreq; info.Limits.VNA.maxPoints = packet.info.limits_maxPoints; info.Limits.VNA.minIFBW = packet.info.limits_minIFBW; info.Limits.VNA.maxIFBW = packet.info.limits_maxIFBW; info.Limits.VNA.mindBm = (double) packet.info.limits_cdbm_min / 100; info.Limits.VNA.maxdBm = (double) packet.info.limits_cdbm_max / 100; info.Limits.Generator.ports = packet.info.num_ports; info.Limits.Generator.minFreq = packet.info.limits_minFreq; info.Limits.Generator.maxFreq = packet.info.limits_maxFreq; info.Limits.Generator.mindBm = (double) packet.info.limits_cdbm_min / 100; info.Limits.Generator.maxdBm = (double) packet.info.limits_cdbm_max / 100; info.Limits.SA.ports = packet.info.num_ports; info.Limits.SA.minFreq = packet.info.limits_minFreq; info.Limits.SA.maxFreq = packet.info.limits_maxFreq; info.Limits.SA.minRBW = packet.info.limits_minRBW; info.Limits.SA.maxRBW = packet.info.limits_maxRBW; info.Limits.SA.mindBm = (double) packet.info.limits_cdbm_min / 100; info.Limits.SA.maxdBm = (double) packet.info.limits_cdbm_max / 100; limits_maxAmplitudePoints = packet.info.limits_maxAmplitudePoints; emit InfoUpdated(); } break; case Protocol::PacketType::DeviceStatus: lastStatus = packet.status; emit StatusUpdated(); emit FlagsUpdated(); break; case Protocol::PacketType::VNADatapoint: { VNAMeasurement m; Protocol::VNADatapoint<32> *res = packet.VNAdatapoint; m.pointNum = res->pointNum; m.Z0 = 50.0; if(zerospan) { m.us = res->us; } else { m.frequency = res->frequency; m.dBm = (double) res->cdBm / 100; } for(auto map : portStageMapping) { // map.first is the port (starts at one) // map.second is the stage at which this port had the stimulus (starts at zero) complex ref = res->getValue(map.second, map.first-1, true); for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) { complex input = res->getValue(map.second, i-1, false); if(!std::isnan(ref.real()) && !std::isnan(input.real())) { // got both required measurements QString name = "S"+QString::number(i)+QString::number(map.first); m.measurements[name] = input / ref; } if(captureRawReceiverValues) { QString name = "RawPort"+QString::number(i)+"Stage"+QString::number(map.second); m.measurements[name] = input; name = "RawPort"+QString::number(i)+"Stage"+QString::number(map.second)+"Ref"; m.measurements[name] = res->getValue(map.second, i, true); } } } delete res; emit VNAmeasurementReceived(m); } break; case Protocol::PacketType::SpectrumAnalyzerResult: { SAMeasurement m; m.pointNum = packet.spectrumResult.pointNum; if(zerospan) { m.us = packet.spectrumResult.us; } else { m.frequency = packet.spectrumResult.frequency; } m.measurements["PORT1"] = packet.spectrumResult.port1; m.measurements["PORT2"] = packet.spectrumResult.port2; emit SAmeasurementReceived(m); } break; default: break; } } QString LibreVNADriver::hardwareVersionToString(uint8_t version) { switch(version) { case 1: return "1"; case 255: return "PT"; default: return "Unknown"; } } unsigned int LibreVNADriver::getMaxAmplitudePoints() const { return limits_maxAmplitudePoints; } QString LibreVNADriver::getFirmwareMagicString() { switch(hardwareVersion) { case 1: return "VNA!"; case 255: return "VNPT"; default: return "XXXX"; } } bool LibreVNADriver::sendWithoutPayload(Protocol::PacketType type, std::function cb) { Protocol::PacketInfo p; p.type = type; return SendPacket(p, cb); }