mirror of
https://github.com/jankae/LibreVNA.git
synced 2025-12-06 07:12:10 +01:00
593 lines
21 KiB
C++
593 lines
21 KiB
C++
#include <HW_HAL.hpp>
|
|
#include <VNA.hpp>
|
|
#include "Si5351C.hpp"
|
|
#include "max2871.hpp"
|
|
#include "main.h"
|
|
#include "delay.hpp"
|
|
#include "FPGA/FPGA.hpp"
|
|
#include <complex>
|
|
#include "Exti.hpp"
|
|
#include "Hardware.hpp"
|
|
#include "Communication.h"
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
#include "Util.hpp"
|
|
#include "usb.h"
|
|
|
|
#define LOG_LEVEL LOG_LEVEL_INFO
|
|
#define LOG_MODULE "VNA"
|
|
#include "Log.h"
|
|
|
|
static Protocol::SweepSettings settings;
|
|
static uint16_t pointCnt;
|
|
static bool excitingPort1;
|
|
static Protocol::Datapoint data;
|
|
static bool active = false;
|
|
static Si5351C::DriveStrength fixedPowerLowband;
|
|
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];
|
|
};
|
|
|
|
using ADCTableEntry = struct {
|
|
uint16_t pointCnt;
|
|
uint8_t adcPrescaler;
|
|
};
|
|
|
|
static constexpr uint16_t IFTableNumEntries = 500;
|
|
static IFTableEntry IFTable[IFTableNumEntries];
|
|
static uint16_t IFTableIndexCnt = 0;
|
|
|
|
static constexpr uint16_t ADCTableNumEntries = 500;
|
|
static ADCTableEntry ADCTable[ADCTableNumEntries];
|
|
static uint16_t ADCTableIndexCnt = 0;
|
|
|
|
static constexpr float alternativeSamplerate = 914285.7143f;
|
|
static constexpr uint8_t alternativePrescaler = 102400000UL / alternativeSamplerate;
|
|
static_assert(alternativePrescaler * alternativeSamplerate == 102400000UL, "alternative ADCSamplerate can not be reached exactly");
|
|
static constexpr uint16_t alternativePhaseInc = 4096 * HW::IF2 / alternativeSamplerate;
|
|
static_assert(alternativePhaseInc * alternativeSamplerate == 4096 * HW::IF2, "DFT can not be computed for 2.IF when using alternative samplerate");
|
|
|
|
// Constants for USB buffer overflow prevention
|
|
static constexpr uint16_t maxPointsBetweenHalts = 40;
|
|
static constexpr uint32_t reservedUSBbuffer = maxPointsBetweenHalts * (sizeof(Protocol::Datapoint) + 8 /*USB packet overhead*/);
|
|
|
|
using namespace HWHAL;
|
|
|
|
bool VNA::Setup(Protocol::SweepSettings s) {
|
|
VNA::Stop();
|
|
vTaskDelay(5);
|
|
HW::SetMode(HW::Mode::VNA);
|
|
if(s.excitePort1 == 0 && s.excitePort2 == 0) {
|
|
// both ports disabled, nothing to do
|
|
HW::SetIdle();
|
|
active = false;
|
|
return false;
|
|
}
|
|
settings = s;
|
|
// Abort possible active sweep first
|
|
FPGA::SetMode(FPGA::Mode::FPGA);
|
|
uint16_t points = settings.points <= FPGA::MaxPoints ? settings.points : FPGA::MaxPoints;
|
|
// Configure sweep
|
|
FPGA::SetNumberOfPoints(points);
|
|
uint32_t samplesPerPoint = (HW::ADCSamplerate / s.if_bandwidth);
|
|
// round up to next multiple of 16 (16 samples are spread across 5 IF2 periods)
|
|
if(samplesPerPoint%16) {
|
|
samplesPerPoint += 16 - samplesPerPoint%16;
|
|
}
|
|
actualBandwidth = HW::ADCSamplerate / samplesPerPoint;
|
|
// has to be one less than actual number of samples
|
|
FPGA::SetSamplesPerPoint(samplesPerPoint);
|
|
|
|
// reset unlevel flag if it was set from a previous sweep/mode
|
|
HW::SetOutputUnlevel(false);
|
|
// Start with average level
|
|
auto cdbm = (s.cdbm_excitation_start + s.cdbm_excitation_stop) / 2;
|
|
// correct for port 1, assumes port 2 is identical
|
|
auto centerFreq = (s.f_start + s.f_stop) / 2;
|
|
// force calculation of amplitude setting for PLL, even with lower frequencies
|
|
if(centerFreq < HW::BandSwitchFrequency) {
|
|
centerFreq = HW::BandSwitchFrequency;
|
|
}
|
|
auto amplitude = HW::GetAmplitudeSettings(cdbm, centerFreq, true, false);
|
|
if(amplitude.unlevel) {
|
|
HW::SetOutputUnlevel(true);
|
|
}
|
|
|
|
uint8_t fixedAttenuatorHighband = amplitude.attenuator;
|
|
Source.SetPowerOutA(amplitude.highBandPower, true);
|
|
|
|
// amplitude calculation for lowband
|
|
amplitude = HW::GetAmplitudeSettings(cdbm, HW::BandSwitchFrequency / 2, true, false);
|
|
if(amplitude.unlevel) {
|
|
HW::SetOutputUnlevel(true);
|
|
}
|
|
uint8_t fixedAttenuatorLowband = amplitude.attenuator;
|
|
fixedPowerLowband = amplitude.lowBandPower;
|
|
|
|
FPGA::WriteMAX2871Default(Source.GetRegisters());
|
|
|
|
uint32_t last_LO2 = HW::IF1 - HW::IF2;
|
|
Si5351.SetCLK(SiChannel::Port1LO2, last_LO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
|
Si5351.SetCLK(SiChannel::Port2LO2, last_LO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
|
Si5351.SetCLK(SiChannel::RefLO2, last_LO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
|
Si5351.ResetPLL(Si5351C::PLL::B);
|
|
|
|
IFTableIndexCnt = 0;
|
|
ADCTableIndexCnt = 0;
|
|
uint8_t last_ADC_prescaler = HW::ADCprescaler;
|
|
|
|
bool last_lowband = false;
|
|
|
|
uint16_t pointsWithoutHalt = 0;
|
|
|
|
// 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);
|
|
int16_t power = s.cdbm_excitation_start + (s.cdbm_excitation_stop - s.cdbm_excitation_start) * i / (points - 1);
|
|
freq = Cal::FrequencyCorrectionToDevice(freq);
|
|
|
|
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.
|
|
|
|
bool needs_halt = false;
|
|
uint64_t actualSourceFreq;
|
|
bool lowband = false;
|
|
if (freq < HW::BandSwitchFrequency) {
|
|
needs_halt = true;
|
|
lowband = true;
|
|
actualSourceFreq = freq;
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
uint64_t LOFreq = freq + HW::IF1;
|
|
|
|
// check for collision between IF and point frequency
|
|
if(s.suppressPeaks && IFTableIndexCnt < IFTableNumEntries - 1) {
|
|
static constexpr int8_t max_multiple = 3;
|
|
bool needs_alternate_IF = false;
|
|
for(int8_t i=-max_multiple;i<=max_multiple;i++) {
|
|
if(i==0) {
|
|
continue;
|
|
}
|
|
for(int8_t j=-max_multiple;j<=max_multiple;j++) {
|
|
if(j==0) {
|
|
continue;
|
|
}
|
|
float m_IF, m_freq;
|
|
if(i<0) {
|
|
m_IF = (float) HW::IF1 / -i;
|
|
} else {
|
|
m_IF = HW::IF1 * i;
|
|
}
|
|
if(j<0) {
|
|
m_freq = (float) freq / -j;
|
|
} else {
|
|
m_freq = freq * j;
|
|
}
|
|
float ratio = m_IF / m_freq;
|
|
if(ratio >= 0.97f && ratio <= 1.03f) {
|
|
needs_alternate_IF = true;
|
|
break;
|
|
}
|
|
}
|
|
if(needs_alternate_IF) {
|
|
break;
|
|
}
|
|
}
|
|
if(needs_alternate_IF) {
|
|
LOFreq += 2150000;
|
|
}
|
|
}
|
|
|
|
// if(s.suppressPeaks && IFTableIndexCnt < IFTableNumEntries - 1) {
|
|
// bool ADC_alias;
|
|
// static constexpr uint64_t max_LO_shift = 2000000;
|
|
// static constexpr uint64_t LO_shift_inc = 71777;
|
|
// static constexpr uint32_t max_mixing_freq = 30000000;
|
|
// do {
|
|
// ADC_alias = false;
|
|
// static uint8_t max_harmonic_LO1 = 5;
|
|
// static uint8_t max_harmonic_LO2 = 97;
|
|
// for (uint64_t harmonic_1LO = 1; harmonic_1LO <= max_harmonic_LO1;
|
|
// harmonic_1LO+=2) {
|
|
// for (uint64_t harmonic_2LO = 1; harmonic_2LO <= max_harmonic_LO2;
|
|
// harmonic_2LO+=2) {
|
|
// int64_t harmonic_freq_1LO = harmonic_1LO * LOFreq;
|
|
// int64_t harmonic_freq_2LO = harmonic_2LO * (HW::IF1 - HW::IF2);
|
|
//
|
|
// if (harmonic_freq_2LO > harmonic_freq_1LO + max_mixing_freq) {
|
|
// break;
|
|
// }
|
|
//
|
|
// uint64_t mixing_freq = llabs(harmonic_freq_1LO - harmonic_freq_2LO);
|
|
// if(mixing_freq > max_mixing_freq) {
|
|
// // ADC filter will take care of frequencies this high
|
|
// continue;
|
|
// }
|
|
// if(abs(Util::Alias(mixing_freq, HW::ADCSamplerate) - HW::IF2) <= actualBandwidth * 2) {
|
|
// // this frequency point aliases into the final IF frequency, need to shift ADC samplerate
|
|
// LOG_INFO("ADC alias with f=%lu%06luHz at point %lu (%lu%06luHz), 1.LO harmonic: %d, 2.LO harmonic: %d",
|
|
// (uint32_t ) (mixing_freq / 1000000),
|
|
// (uint32_t ) (mixing_freq % 1000000),i, (uint32_t ) (freq / 1000000),
|
|
// (uint32_t ) (freq % 1000000), (uint16_t) harmonic_1LO, (uint16_t) harmonic_2LO);
|
|
// ADC_alias = true;
|
|
// // select different 1.LO
|
|
// LOFreq -= LO_shift_inc;
|
|
// break;
|
|
// }
|
|
// }
|
|
// if(ADC_alias) {
|
|
// // already detected a problem, no need for further loop execution
|
|
// break;
|
|
// }
|
|
// }
|
|
// } while(ADC_alias && LOFreq >= freq + HW::IF1 - (max_LO_shift - LO_shift_inc));
|
|
// }
|
|
|
|
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;
|
|
if(IFdeviation > actualBandwidth / 2) {
|
|
needs_LO2_shift = true;
|
|
}
|
|
if (s.suppressPeaks && needs_LO2_shift) {
|
|
if (IFTableIndexCnt < IFTableNumEntries) {
|
|
// still room in table
|
|
needs_halt = true;
|
|
IFTable[IFTableIndexCnt].pointCnt = i;
|
|
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 (1.LO: %lu%06luHz, 1.IF: %lu%06luHz)",
|
|
last_LO2, i, (uint32_t ) (freq / 1000000),
|
|
(uint32_t ) (freq % 1000000), (uint32_t ) (actualLO1 / 1000000),
|
|
(uint32_t ) (actualLO1 % 1000000), (uint32_t ) (actualFirstIF / 1000000),
|
|
(uint32_t ) (actualFirstIF % 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
|
|
Si5351.ReadRawCLKConfig(SiChannel::RefLO2, IFTable[IFTableIndexCnt].clkconfig);
|
|
IFTableIndexCnt++;
|
|
needs_LO2_shift = false;
|
|
}
|
|
}
|
|
if(needs_LO2_shift) {
|
|
// if shift is still needed either peak suppression is disabled or no more room in IFTable was available
|
|
LOG_WARN(
|
|
"PLL deviation of %luHz for measurement at %lu%06luHz, will cause a peak",
|
|
IFdeviation, (uint32_t ) (freq / 1000000), (uint32_t ) (freq % 1000000));
|
|
}
|
|
|
|
// Check if we have an ADC alias problem at this frequency. The 1.LO (and its harmonics) will mix with
|
|
// the 2.LO (and its harmonics) in the 2.mixer stage. The mixing product may alias to the final IF in
|
|
// the ADCs. This would cause a peak at this frequency in the spectrum
|
|
// bool ADC_alias = false;
|
|
// static uint8_t max_harmonic = 3;
|
|
// for (uint64_t harmonic_1LO = 1; harmonic_1LO <= max_harmonic;
|
|
// harmonic_1LO++) {
|
|
// for (uint64_t harmonic_2LO = 1; harmonic_2LO <= max_harmonic;
|
|
// harmonic_2LO++) {
|
|
// uint64_t harmonic_freq_1LO = harmonic_1LO * actualLO1;
|
|
// uint64_t harmonic_freq_2LO = harmonic_2LO * last_LO2;
|
|
//
|
|
// uint64_t mixing_freq = abs(harmonic_freq_1LO - harmonic_freq_2LO);
|
|
// if(abs(Util::Alias(mixing_freq, HW::ADCSamplerate) - HW::IF2) <= actualBandwidth * 2) {
|
|
// // this frequency point aliases into the final IF frequency, need to shift ADC samplerate
|
|
// ADC_alias = true;
|
|
// break;
|
|
// }
|
|
// }
|
|
// if(ADC_alias) {
|
|
// // already detected a problem, no need for further loop execution
|
|
// break;
|
|
// }
|
|
// }
|
|
//
|
|
// uint8_t required_prescaler = HW::ADCprescaler;
|
|
// if(ADC_alias) {
|
|
// required_prescaler = alternativePrescaler;
|
|
// }
|
|
//
|
|
// if(required_prescaler != last_ADC_prescaler) {
|
|
// // needs to shift ADC prescaler at this point
|
|
// if (ADCTableIndexCnt < ADCTableNumEntries) {
|
|
// // still room in table
|
|
// needs_halt = true;
|
|
// ADCTable[ADCTableIndexCnt].pointCnt = i;
|
|
// if(ADCTableIndexCnt < ADCTableNumEntries - 1) {
|
|
// ADCTable[ADCTableIndexCnt].adcPrescaler = required_prescaler;
|
|
// } else {
|
|
// // last entry in ADC table, revert ADC prescaler to default
|
|
// ADCTable[ADCTableIndexCnt].adcPrescaler = HW::ADCprescaler;
|
|
// }
|
|
// last_ADC_prescaler = ADCTable[ADCTableIndexCnt].adcPrescaler;
|
|
// LOG_INFO("Changing ADC prescaler to %d at point %lu (%lu%06luHz) to prevent alias.",
|
|
// ADCTable[ADCTableIndexCnt].adcPrescaler, i, (uint32_t ) (freq / 1000000),
|
|
// (uint32_t ) (freq % 1000000));
|
|
// ADCTableIndexCnt++;
|
|
// }
|
|
// }
|
|
|
|
// halt on regular intervals to prevent USB buffer overflow
|
|
if(!needs_halt) {
|
|
pointsWithoutHalt++;
|
|
if(pointsWithoutHalt > maxPointsBetweenHalts) {
|
|
needs_halt = true;
|
|
}
|
|
}
|
|
if(needs_halt) {
|
|
pointsWithoutHalt = 0;
|
|
}
|
|
|
|
uint8_t attenuator = freq >= HW::BandSwitchFrequency ? fixedAttenuatorHighband : fixedAttenuatorLowband;
|
|
if(!s.fixedPowerSetting) {
|
|
// adapt power level throughout the sweep
|
|
amplitude = HW::GetAmplitudeSettings(power, freq, true, false);
|
|
if(freq >= HW::BandSwitchFrequency) {
|
|
Source.SetPowerOutA(amplitude.highBandPower, true);
|
|
}
|
|
if(amplitude.unlevel) {
|
|
HW::SetOutputUnlevel(true);
|
|
}
|
|
attenuator = amplitude.attenuator;
|
|
}
|
|
|
|
FPGA::WriteSweepConfig(i, lowband, Source.GetRegisters(),
|
|
LO1.GetRegisters(), attenuator, freq, FPGA::SettlingTime::us20,
|
|
FPGA::Samples::SPPRegister, needs_halt);
|
|
last_lowband = lowband;
|
|
}
|
|
// revert clk configuration to previous value (might have been changed in sweep calculation)
|
|
Si5351.SetCLK(SiChannel::RefLO2, HW::IF1 - HW::IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
|
Si5351.ResetPLL(Si5351C::PLL::B);
|
|
if (s.f_start >= HW::BandSwitchFrequency) {
|
|
// first point already uses the MAX2871 as the source and (probably) does not have the "halt sweep" bit
|
|
// set. Give Si5351 some time to ramp up the 2.LO, otherwise the first samples might be invalid
|
|
Delay::us(1300);
|
|
}
|
|
// Enable mixers/amplifier/PLLs
|
|
FPGA::SetWindow(FPGA::Window::Hann);
|
|
FPGA::Enable(FPGA::Periphery::Port1Mixer);
|
|
FPGA::Enable(FPGA::Periphery::Port2Mixer);
|
|
FPGA::Enable(FPGA::Periphery::RefMixer);
|
|
FPGA::Enable(FPGA::Periphery::Amplifier);
|
|
FPGA::Enable(FPGA::Periphery::SourceChip);
|
|
FPGA::Enable(FPGA::Periphery::SourceRF);
|
|
FPGA::Enable(FPGA::Periphery::LO1Chip);
|
|
FPGA::Enable(FPGA::Periphery::LO1RF);
|
|
FPGA::Enable(FPGA::Periphery::ExcitePort1, s.excitePort1);
|
|
FPGA::Enable(FPGA::Periphery::ExcitePort2, s.excitePort2);
|
|
FPGA::Enable(FPGA::Periphery::PortSwitch);
|
|
pointCnt = 0;
|
|
// starting port depends on whether port 1 is active in sweep
|
|
excitingPort1 = s.excitePort1;
|
|
// invalidate first invalid entry of IFTable, preventing switching of 2.LO in halted callback
|
|
IFTable[IFTableIndexCnt].pointCnt = 0xFFFF;
|
|
ADCTable[ADCTableIndexCnt].pointCnt = 0xFFFF;
|
|
// reset table index
|
|
IFTableIndexCnt = 0;
|
|
ADCTableIndexCnt = 0;
|
|
|
|
adcShifted = false;
|
|
active = true;
|
|
// Enable new data and sweep halt interrupt
|
|
FPGA::EnableInterrupt(FPGA::Interrupt::NewData);
|
|
FPGA::EnableInterrupt(FPGA::Interrupt::SweepHalted);
|
|
// Start the sweep
|
|
FPGA::StartSweep();
|
|
return true;
|
|
}
|
|
|
|
static void PassOnData() {
|
|
Protocol::PacketInfo info;
|
|
info.type = Protocol::PacketType::Datapoint;
|
|
info.datapoint = data;
|
|
Communication::Send(info);
|
|
}
|
|
|
|
bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
|
|
if(!active) {
|
|
return false;
|
|
}
|
|
if(result.pointNum != pointCnt || !result.activePort != excitingPort1) {
|
|
LOG_WARN("Indicated point does not match (%u != %u, %d != %d)", result.pointNum, pointCnt, result.activePort, !excitingPort1);
|
|
FPGA::AbortSweep();
|
|
return false;
|
|
}
|
|
// normal sweep mode
|
|
auto port1_raw = std::complex<float>(result.P1I, result.P1Q);
|
|
auto port2_raw = std::complex<float>(result.P2I, result.P2Q);
|
|
auto ref = std::complex<float>(result.RefI, result.RefQ);
|
|
auto port1 = port1_raw / ref;
|
|
auto port2 = port2_raw / ref;
|
|
data.pointNum = pointCnt;
|
|
data.frequency = settings.f_start + (settings.f_stop - settings.f_start) * pointCnt / (settings.points - 1);
|
|
data.cdbm = settings.cdbm_excitation_start + (settings.cdbm_excitation_stop - settings.cdbm_excitation_start) * pointCnt / (settings.points - 1);
|
|
if(excitingPort1) {
|
|
data.real_S11 = port1.real();
|
|
data.imag_S11 = port1.imag();
|
|
data.real_S21 = port2.real();
|
|
data.imag_S21 = port2.imag();
|
|
} else {
|
|
data.real_S12 = port1.real();
|
|
data.imag_S12 = port1.imag();
|
|
data.real_S22 = port2.real();
|
|
data.imag_S22 = port2.imag();
|
|
}
|
|
// figure out whether this sweep point is complete and which port gets excited next
|
|
bool pointComplete = false;
|
|
if(settings.excitePort1 == 1 && settings.excitePort2 == 1) {
|
|
// point is complete when port 2 was active
|
|
pointComplete = !excitingPort1;
|
|
// next measurement will be from other port
|
|
excitingPort1 = !excitingPort1;
|
|
} else {
|
|
// only one port active, point is complete after every measurement
|
|
pointComplete = true;
|
|
}
|
|
if(pointComplete) {
|
|
STM::DispatchToInterrupt(PassOnData);
|
|
pointCnt++;
|
|
if (pointCnt >= settings.points) {
|
|
// reached end of sweep, start again
|
|
pointCnt = 0;
|
|
IFTableIndexCnt = 0;
|
|
// request to trigger work function
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void VNA::Work() {
|
|
// end of sweep
|
|
HW::Ref::update();
|
|
// Compile info packet
|
|
Protocol::PacketInfo packet;
|
|
packet.type = Protocol::PacketType::DeviceInfo;
|
|
HW::fillDeviceInfo(&packet.info, true);
|
|
Communication::Send(packet);
|
|
// do not reset unlevel flag here, as it is calculated only once at the setup of the sweep
|
|
// Start next sweep
|
|
FPGA::StartSweep();
|
|
}
|
|
|
|
void VNA::SweepHalted() {
|
|
if(!active) {
|
|
return;
|
|
}
|
|
LOG_DEBUG("Halted before point %d", pointCnt);
|
|
// Check if IF table has entry at this point
|
|
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);
|
|
Si5351.ResetPLL(Si5351C::PLL::B);
|
|
IFTableIndexCnt++;
|
|
// PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point
|
|
Delay::us(1300);
|
|
}
|
|
uint64_t frequency = settings.f_start
|
|
+ (settings.f_stop - settings.f_start) * pointCnt
|
|
/ (settings.points - 1);
|
|
int16_t power = settings.cdbm_excitation_start
|
|
+ (settings.cdbm_excitation_stop - settings.cdbm_excitation_start)
|
|
* pointCnt / (settings.points - 1);
|
|
bool adcShiftRequired = false;
|
|
if (frequency < HW::BandSwitchFrequency) {
|
|
auto driveStrength = fixedPowerLowband;
|
|
if(!settings.fixedPowerSetting) {
|
|
auto amplitude = HW::GetAmplitudeSettings(power, frequency, true, false);
|
|
// attenuator value has already been set in sweep setup
|
|
driveStrength = amplitude.lowBandPower;
|
|
}
|
|
|
|
// need the Si5351 as Source
|
|
Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B, driveStrength);
|
|
if (pointCnt == 0) {
|
|
// First point in sweep, enable CLK
|
|
Si5351.Enable(SiChannel::LowbandSource);
|
|
FPGA::Disable(FPGA::Periphery::SourceRF);
|
|
Delay::us(1300);
|
|
}
|
|
|
|
// // At low frequencies the 1.LO feedthrough mixes with the 2.LO in the second mixer.
|
|
// // Depending on the stimulus frequency, the resulting mixing product might alias to the 2.IF
|
|
// // in the ADC which causes a spike. Check for this and shift the ADC sampling frequency if necessary
|
|
// uint32_t LO_mixing = (HW::IF1 + frequency) - (HW::IF1 - HW::IF2);
|
|
// if(abs(Util::Alias(LO_mixing, HW::ADCSamplerate) - HW::IF2) <= actualBandwidth * 2) {
|
|
// // the image is in or near the IF bandwidth and would cause a peak
|
|
// // Use a slightly different ADC samplerate
|
|
// adcShiftRequired = true;
|
|
// }
|
|
} else if(!FPGA::IsEnabled(FPGA::Periphery::SourceRF)){
|
|
// first sweep point in highband is also halted, disable lowband source
|
|
Si5351.Disable(SiChannel::LowbandSource);
|
|
FPGA::Enable(FPGA::Periphery::SourceRF);
|
|
}
|
|
|
|
// if(adcShiftRequired) {
|
|
// FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, alternativePrescaler);
|
|
// FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, alternativePhaseInc);
|
|
// adcShifted = true;
|
|
// } else if(adcShifted) {
|
|
// // reset to default value
|
|
// FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, HW::ADCprescaler);
|
|
// FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, HW::DFTphaseInc);
|
|
// adcShifted = false;
|
|
// }
|
|
// // Check if IF table has entry at this point
|
|
// if (ADCTableIndexCnt < ADCTableNumEntries && ADCTable[ADCTableIndexCnt].pointCnt == pointCnt) {
|
|
// FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, ADCTable[ADCTableIndexCnt].adcPrescaler);
|
|
// FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, ADCTable[ADCTableIndexCnt].adcPrescaler * 10);
|
|
// ADCTableIndexCnt++;
|
|
// }
|
|
|
|
if(usb_available_buffer() >= reservedUSBbuffer) {
|
|
// enough space available, can resume immediately
|
|
FPGA::ResumeHaltedSweep();
|
|
} else {
|
|
// USB buffer could potentially overflow before next halted point, wait until more space is available.
|
|
// This function is called from a low level interrupt, need to dispatch to lower priority to allow USB
|
|
// handling to continue
|
|
STM::DispatchToInterrupt([](){
|
|
uint32_t start = HAL_GetTick();
|
|
while(usb_available_buffer() < reservedUSBbuffer) {
|
|
if(HAL_GetTick() - start > 100) {
|
|
// still no buffer space after some time, something more serious must have gone wrong
|
|
// -> abort sweep and return to idle
|
|
usb_clear_buffer();
|
|
FPGA::AbortSweep();
|
|
HW::SetIdle();
|
|
return;
|
|
}
|
|
}
|
|
FPGA::ResumeHaltedSweep();
|
|
});
|
|
}
|
|
}
|
|
|
|
void VNA::Stop() {
|
|
active = false;
|
|
FPGA::AbortSweep();
|
|
}
|