diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 6422a73..5eb2de2 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -817,8 +817,15 @@ void VNA::StartCalibrationMeasurement(Calibration::Measurement m) void VNA::ConstrainAndUpdateFrequencies() { - if(settings.f_stop > Device::Info().limits_maxFreq) { - settings.f_stop = Device::Info().limits_maxFreq; + auto pref = Preferences::getInstance(); + double maxFreq; + if(pref.Acquisition.harmonicMixing) { + maxFreq = Device::Info().limits_maxFreqHarmonic; + } else { + maxFreq = Device::Info().limits_maxFreq; + } + if(settings.f_stop > maxFreq) { + settings.f_stop = maxFreq; } if(settings.f_start > settings.f_stop) { settings.f_start = settings.f_stop; diff --git a/Software/PC_Application/preferences.cpp b/Software/PC_Application/preferences.cpp index d1bc3b0..10dff39 100644 --- a/Software/PC_Application/preferences.cpp +++ b/Software/PC_Application/preferences.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "CustomWidgets/informationbox.h" using namespace std; @@ -115,6 +116,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : p->Startup.SA.signalID = ui->StartupSASignalID->isChecked(); p->Acquisition.alwaysExciteBothPorts = ui->AcquisitionAlwaysExciteBoth->isChecked(); p->Acquisition.suppressPeaks = ui->AcquisitionSuppressPeaks->isChecked(); + p->Acquisition.harmonicMixing = ui->AcquisitionUseHarmonic->isChecked(); p->Acquisition.useDFTinSAmode = ui->AcquisitionUseDFT->isChecked(); p->Acquisition.RBWLimitForDFT = ui->AcquisitionDFTlimitRBW->value(); p->General.graphColors.background = ui->GeneralGraphBackground->getColor(); @@ -124,6 +126,16 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : }); setInitialGUIState(); + + connect(ui->AcquisitionUseHarmonic, &QCheckBox::toggled, [=](bool enabled) { + if(enabled) { + InformationBox::ShowMessage("Harmonic Mixing", "When harmonic mixing is enabled, the frequency range of the VNA is 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. Performance below 6GHz is not affected by this setting"); + } + }); } PreferencesDialog::~PreferencesDialog() @@ -157,6 +169,7 @@ void PreferencesDialog::setInitialGUIState() ui->AcquisitionAlwaysExciteBoth->setChecked(p->Acquisition.alwaysExciteBothPorts); ui->AcquisitionSuppressPeaks->setChecked(p->Acquisition.suppressPeaks); + ui->AcquisitionUseHarmonic->setChecked(p->Acquisition.harmonicMixing); ui->AcquisitionUseDFT->setChecked(p->Acquisition.useDFTinSAmode); ui->AcquisitionDFTlimitRBW->setValue(p->Acquisition.RBWLimitForDFT); diff --git a/Software/PC_Application/preferences.h b/Software/PC_Application/preferences.h index 5ee9209..9759524 100644 --- a/Software/PC_Application/preferences.h +++ b/Software/PC_Application/preferences.h @@ -44,6 +44,7 @@ public: struct { bool alwaysExciteBothPorts; bool suppressPeaks; + bool harmonicMixing; bool useDFTinSAmode; double RBWLimitForDFT; } Acquisition; @@ -62,7 +63,7 @@ private: QString name; QVariant def; }; - const std::array descr = {{ + const std::array descr = {{ {&Startup.ConnectToFirstDevice, "Startup.ConnectToFirstDevice", true}, {&Startup.RememberSweepSettings, "Startup.RememberSweepSettings", false}, {&Startup.DefaultSweep.start, "Startup.DefaultSweep.start", 1000000.0}, @@ -82,6 +83,7 @@ private: {&Startup.SA.signalID, "Startup.SA.signalID", true}, {&Acquisition.alwaysExciteBothPorts, "Acquisition.alwaysExciteBothPorts", true}, {&Acquisition.suppressPeaks, "Acquisition.suppressPeaks", true}, + {&Acquisition.harmonicMixing, "Acquisition.harmonicMixing", false}, {&Acquisition.useDFTinSAmode, "Acquisition.useDFTinSAmode", true}, {&Acquisition.RBWLimitForDFT, "Acquisition.RBWLimitForDFT", 3000.0}, {&General.graphColors.background, "General.graphColors.background", QColor(Qt::black)}, diff --git a/Software/PC_Application/preferencesdialog.ui b/Software/PC_Application/preferencesdialog.ui index 873e3d0..0ed73e4 100644 --- a/Software/PC_Application/preferencesdialog.ui +++ b/Software/PC_Application/preferencesdialog.ui @@ -479,6 +479,13 @@ + + + + Use harmonic mixing + + + diff --git a/Software/VNA_embedded/Application/Communication/Protocol.cpp b/Software/VNA_embedded/Application/Communication/Protocol.cpp index aee91fd..34bac21 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.cpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.cpp @@ -265,6 +265,7 @@ static Protocol::DeviceInfo DecodeDeviceInfo(uint8_t *buf) { e.get(d.limits_minRBW); e.get(d.limits_maxRBW); e.get(d.limits_maxAmplitudePoints); + e.get(d.limits_maxFreqHarmonic); return d; } static int16_t EncodeDeviceInfo(Protocol::DeviceInfo d, uint8_t *buf, @@ -295,6 +296,7 @@ static int16_t EncodeDeviceInfo(Protocol::DeviceInfo d, uint8_t *buf, e.add(d.limits_minRBW); e.add(d.limits_maxRBW); e.add(d.limits_maxAmplitudePoints); + e.add(d.limits_maxFreqHarmonic); return e.getSize(); } diff --git a/Software/VNA_embedded/Application/Communication/Protocol.hpp b/Software/VNA_embedded/Application/Communication/Protocol.hpp index 6b9d9bb..a4b98d4 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 = 3; +static constexpr uint16_t Version = 4; // When changing/adding/removing variables from these structs also adjust the decode/encode functions in Protocol.cpp @@ -66,6 +66,7 @@ using DeviceInfo = struct _deviceInfo { uint32_t limits_minRBW; uint32_t limits_maxRBW; uint8_t limits_maxAmplitudePoints; + uint64_t limits_maxFreqHarmonic; }; using ManualStatus = struct _manualstatus { diff --git a/Software/VNA_embedded/Application/Hardware.hpp b/Software/VNA_embedded/Application/Hardware.hpp index a38c2db..e4bd642 100644 --- a/Software/VNA_embedded/Application/Hardware.hpp +++ b/Software/VNA_embedded/Application/Hardware.hpp @@ -73,6 +73,7 @@ static constexpr Protocol::DeviceInfo Info = { .limits_minRBW = (uint32_t) (ADCSamplerate * 2.23f / MaxSamples), .limits_maxRBW = (uint32_t) (ADCSamplerate * 2.23f / MinSamples), .limits_maxAmplitudePoints = AmplitudeCal::maxPoints, + .limits_maxFreqHarmonic = 18000000000, }; enum class Mode { diff --git a/Software/VNA_embedded/Application/VNA.cpp b/Software/VNA_embedded/Application/VNA.cpp index baea37c..760b846 100644 --- a/Software/VNA_embedded/Application/VNA.cpp +++ b/Software/VNA_embedded/Application/VNA.cpp @@ -26,6 +26,9 @@ static bool sourceHighPower; static bool adcShifted; static uint32_t actualBandwidth; +static constexpr uint8_t sourceHarmonic = 5; +static constexpr uint8_t LOHarmonic = 3; + using IFTableEntry = struct { uint16_t pointCnt; uint8_t clkconfig[8]; @@ -105,7 +108,13 @@ bool VNA::Setup(Protocol::SweepSettings s) { // Transfer PLL configuration to FPGA for (uint16_t i = 0; i < points; i++) { + bool harmonic_mixing = false; uint64_t freq = s.f_start + (s.f_stop - s.f_start) * i / (points - 1); + + if(freq > 6000000000ULL) { + harmonic_mixing = true; + } + // SetFrequency only manipulates the register content in RAM, no SPI communication is done. // No mode-switch of FPGA necessary here. @@ -117,15 +126,30 @@ bool VNA::Setup(Protocol::SweepSettings s) { lowband = true; actualSourceFreq = freq; } else { - Source.SetFrequency(freq); + uint64_t srcFreq = freq; + if(harmonic_mixing) { + srcFreq /= sourceHarmonic; + } + Source.SetFrequency(srcFreq); actualSourceFreq = Source.GetActualFrequency(); + if(harmonic_mixing) { + actualSourceFreq *= sourceHarmonic; + } } if (last_lowband && !lowband) { // additional halt before first highband point to enable highband source needs_halt = true; } - LO1.SetFrequency(freq + HW::IF1); - uint32_t actualFirstIF = LO1.GetActualFrequency() - actualSourceFreq; + uint64_t LOFreq = freq + HW::IF1; + if(harmonic_mixing) { + LOFreq /= LOHarmonic; + } + LO1.SetFrequency(LOFreq); + uint64_t actualLO1 = LO1.GetActualFrequency(); + if(harmonic_mixing) { + actualLO1 *= LOHarmonic; + } + uint32_t actualFirstIF = actualLO1 - actualSourceFreq; uint32_t actualFinalIF = actualFirstIF - last_LO2; uint32_t IFdeviation = abs(actualFinalIF - HW::IF2); bool needs_LO2_shift = false; @@ -137,12 +161,17 @@ bool VNA::Setup(Protocol::SweepSettings s) { // still room in table needs_halt = true; IFTable[IFTableIndexCnt].pointCnt = i; - // Configure LO2 for the changed IF1. This is not necessary right now but it will generate - // the correct clock settings - last_LO2 = actualFirstIF - HW::IF2; - LOG_INFO("Changing 2.LO to %lu at point %lu (%lu%06luHz) to reach correct 2.IF frequency", - last_LO2, i, (uint32_t ) (freq / 1000000), - (uint32_t ) (freq % 1000000)); + if(IFTableIndexCnt < IFTableNumEntries - 1) { + // Configure LO2 for the changed IF1. This is not necessary right now but it will generate + // the correct clock settings + last_LO2 = actualFirstIF - HW::IF2; + LOG_INFO("Changing 2.LO to %lu at point %lu (%lu%06luHz) to reach correct 2.IF frequency", + last_LO2, i, (uint32_t ) (freq / 1000000), + (uint32_t ) (freq % 1000000)); + } else { + // last entry in IF table, revert LO2 to default + last_LO2 = HW::IF1 - HW::IF2; + } Si5351.SetCLK(SiChannel::RefLO2, last_LO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2); // store calculated clock configuration for later change @@ -271,7 +300,7 @@ void VNA::SweepHalted() { } LOG_DEBUG("Halted before point %d", pointCnt); // Check if IF table has entry at this point - if (IFTable[IFTableIndexCnt].pointCnt == pointCnt) { + if (IFTableIndexCnt < IFTableNumEntries && IFTable[IFTableIndexCnt].pointCnt == pointCnt) { Si5351.WriteRawCLKConfig(SiChannel::Port1LO2, IFTable[IFTableIndexCnt].clkconfig); Si5351.WriteRawCLKConfig(SiChannel::Port2LO2, IFTable[IFTableIndexCnt].clkconfig); Si5351.WriteRawCLKConfig(SiChannel::RefLO2, IFTable[IFTableIndexCnt].clkconfig);