Refactoring, better code encapsulation for different operating modes

This commit is contained in:
Jan Käberich 2020-09-17 09:53:52 +02:00
parent d9d00b8c71
commit 00244022c9
13 changed files with 701 additions and 519 deletions

View file

@ -1,3 +1,4 @@
#include <HW_HAL.hpp>
#include <VNA.hpp>
#include "Si5351C.hpp"
#include "max2871.hpp"
@ -6,7 +7,8 @@
#include "FPGA/FPGA.hpp"
#include <complex>
#include "Exti.hpp"
#include "VNA_HAL.hpp"
#include "Hardware.hpp"
#include "Communication.h"
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE "VNA"
@ -17,12 +19,11 @@ static constexpr uint32_t IF1_alternate = 57000000;
static constexpr uint32_t IF2 = 250000;
static VNA::SweepCallback sweepCallback;
static VNA::StatusCallback statusCallback;
static Protocol::SweepSettings settings;
static uint16_t pointCnt;
static bool excitingPort1;
static Protocol::Datapoint data;
static bool manualMode = false;
static bool active = false;
using IFTableEntry = struct {
uint16_t pointCnt;
@ -36,208 +37,21 @@ static uint16_t IFTableIndexCnt = 0;
static constexpr uint32_t BandSwitchFrequency = 25000000;
static uint32_t extOutFreq = 0;
static bool extRefInUse = false;
using namespace HWHAL;
using namespace VNAHAL;
static void HaltedCallback() {
LOG_DEBUG("Halted before point %d", pointCnt);
// Check if IF table has entry at this point
// if (IFTable[IFTableIndexCnt].pointCnt == pointCnt) {
// LOG_DEBUG("Shifting IF to %lu at point %u",
// IFTable[IFTableIndexCnt].IF1, pointCnt);
// Si5351.WriteRawCLKConfig(1, IFTable[IFTableIndexCnt].clkconfig);
// Si5351.WriteRawCLKConfig(4, IFTable[IFTableIndexCnt].clkconfig);
// Si5351.WriteRawCLKConfig(5, IFTable[IFTableIndexCnt].clkconfig);
// Si5351.ResetPLL(Si5351C::PLL::B);
// IFTableIndexCnt++;
// }
uint64_t frequency = settings.f_start
+ (settings.f_stop - settings.f_start) * pointCnt
/ (settings.points - 1);
if (frequency < BandSwitchFrequency) {
// need the Si5351 as Source
Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B,
Si5351C::DriveStrength::mA2);
if (pointCnt == 0) {
// First point in sweep, enable CLK
Si5351.Enable(SiChannel::LowbandSource);
FPGA::Disable(FPGA::Periphery::SourceRF);
}
} else {
// first sweep point in highband is also halted, disable lowband source
Si5351.Disable(SiChannel::LowbandSource);
FPGA::Enable(FPGA::Periphery::SourceRF);
}
FPGA::ResumeHaltedSweep();
}
static void ReadComplete(FPGA::SamplingResult result) {
if(!manualMode) {
// 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);
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) {
if (sweepCallback) {
sweepCallback(data);
}
pointCnt++;
if (pointCnt >= settings.points) {
// reached end of sweep, start again
pointCnt = 0;
IFTableIndexCnt = 0;
}
}
} else {
// Manual control mode, simply pass on raw result
if(statusCallback) {
statusCallback(result);
}
}
}
static void FPGA_Interrupt(void*) {
FPGA::InitiateSampleRead(ReadComplete);
}
bool VNA::Init() {
LOG_DEBUG("Initializing...");
manualMode = false;
Si5351.Init();
// Use Si5351 to generate reference frequencies for other PLLs and ADC
Si5351.SetPLL(Si5351C::PLL::A, 800000000, Si5351C::PLLSource::XTAL);
while(!Si5351.Locked(Si5351C::PLL::A));
Si5351.SetPLL(Si5351C::PLL::B, 800000000, Si5351C::PLLSource::XTAL);
while(!Si5351.Locked(Si5351C::PLL::B));
extRefInUse = 0;
extOutFreq = 0;
Si5351.Disable(SiChannel::ReferenceOut);
// Both MAX2871 get a 100MHz reference
Si5351.SetCLK(SiChannel::Source, 100000000, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Source);
Si5351.SetCLK(SiChannel::LO1, 100000000, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::LO1);
// 16MHz FPGA clock
Si5351.SetCLK(SiChannel::FPGA, 16000000, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::FPGA);
// Generate second LO with Si5351
Si5351.SetCLK(SiChannel::Port1LO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port1LO2);
Si5351.SetCLK(SiChannel::Port2LO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port2LO2);
Si5351.SetCLK(SiChannel::RefLO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::RefLO2);
// PLL reset appears to realign phases of clock signals
Si5351.ResetPLL(Si5351C::PLL::B);
LOG_DEBUG("Si5351 locked");
// FPGA clock is now present, can initialize
if (!FPGA::Init(HaltedCallback)) {
LOG_ERR("Aborting due to uninitialized FPGA");
return false;
}
// Enable new data and sweep halt interrupt
FPGA::EnableInterrupt(FPGA::Interrupt::NewData);
FPGA::EnableInterrupt(FPGA::Interrupt::SweepHalted);
Exti::SetCallback(FPGA_INTR_GPIO_Port, FPGA_INTR_Pin, Exti::EdgeType::Rising, Exti::Pull::Down, FPGA_Interrupt);
// Initialize PLLs and build VCO maps
// enable source synthesizer
FPGA::Enable(FPGA::Periphery::SourceChip);
FPGA::SetMode(FPGA::Mode::SourcePLL);
Source.Init(100000000, false, 1, false);
Source.SetPowerOutA(MAX2871::Power::n4dbm);
// output B is not used
Source.SetPowerOutB(MAX2871::Power::n4dbm, false);
if(!Source.BuildVCOMap()) {
LOG_WARN("Source VCO map failed");
} else {
LOG_INFO("Source VCO map complete");
}
Source.SetFrequency(1000000000);
Source.UpdateFrequency();
LOG_DEBUG("Source temp: %u", Source.GetTemp());
// disable source synthesizer/enable LO synthesizer
FPGA::SetMode(FPGA::Mode::FPGA);
FPGA::Disable(FPGA::Periphery::SourceChip);
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::SetMode(FPGA::Mode::LOPLL);
LO1.Init(100000000, false, 1, false);
LO1.SetPowerOutA(MAX2871::Power::n4dbm);
LO1.SetPowerOutB(MAX2871::Power::n4dbm);
if(!LO1.BuildVCOMap()) {
LOG_WARN("LO1 VCO map failed");
} else {
LOG_INFO("LO1 VCO map complete");
}
LO1.SetFrequency(1000000000 + IF1);
LO1.UpdateFrequency();
LOG_DEBUG("LO temp: %u", LO1.GetTemp());
FPGA::SetMode(FPGA::Mode::FPGA);
// disable both synthesizers
FPGA::Disable(FPGA::Periphery::LO1Chip);
FPGA::WriteMAX2871Default(Source.GetRegisters());
LOG_INFO("Initialized");
FPGA::Enable(FPGA::Periphery::ReadyLED);
return true;
}
bool VNA::ConfigureSweep(Protocol::SweepSettings s, SweepCallback cb) {
if (manualMode) {
// was used in manual mode last, do full initialization before starting sweep
VNA::Init();
}
bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) {
HW::SetMode(HW::Mode::VNA);
if(s.excitePort1 == 0 && s.excitePort2 == 0) {
// both ports disabled, set to idle
SetIdle();
// both ports disabled, nothing to do
HW::SetIdle();
active = false;
return false;
}
sweepCallback = cb;
settings = s;
// Abort possible active sweep first
FPGA::AbortSweep();
FPGA::SetMode(FPGA::Mode::FPGA);
uint16_t points = settings.points <= FPGA::MaxPoints ? settings.points : FPGA::MaxPoints;
// Configure sweep
FPGA::SetNumberOfPoints(points);
@ -264,7 +78,7 @@ bool VNA::ConfigureSweep(Protocol::SweepSettings s, SweepCallback cb) {
// Transfer PLL configuration to FPGA
for (uint16_t i = 0; i < points; i++) {
uint64_t freq = s.f_start + (s.f_stop - s.f_start) * i / (s.points - 1);
uint64_t freq = s.f_start + (s.f_stop - s.f_start) * i / (points - 1);
// SetFrequency only manipulates the register content in RAM, no SPI communication is done.
// No mode-switch of FPGA necessary here.
@ -355,251 +169,116 @@ bool VNA::ConfigureSweep(Protocol::SweepSettings s, SweepCallback cb) {
// starting port depends on whether port 1 is active in sweep
excitingPort1 = s.excitePort1;
IFTableIndexCnt = 0;
active = true;
// Start the sweep
FPGA::StartSweep();
return true;
}
bool VNA::ConfigureManual(Protocol::ManualControl m, StatusCallback cb) {
manualMode = true;
statusCallback = cb;
FPGA::AbortSweep();
// Configure lowband source
if (m.SourceLowEN) {
Si5351.SetCLK(SiChannel::LowbandSource, m.SourceLowFrequency, Si5351C::PLL::B,
(Si5351C::DriveStrength) m.SourceLowPower);
Si5351.Enable(SiChannel::LowbandSource);
} else {
Si5351.Disable(SiChannel::LowbandSource);
bool VNA::MeasurementDone(FPGA::SamplingResult result) {
if(!active) {
return false;
}
// Configure highband source
Source.SetFrequency(m.SourceHighFrequency);
Source.SetPowerOutA((MAX2871::Power) m.SourceHighPower);
// Configure LO1
LO1.SetFrequency(m.LO1Frequency);
// Configure LO2
if(m.LO2EN) {
// Generate second LO with Si5351
Si5351.SetCLK(SiChannel::Port1LO2, m.LO2Frequency, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port1LO2);
Si5351.SetCLK(SiChannel::Port2LO2, m.LO2Frequency, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port2LO2);
Si5351.SetCLK(SiChannel::RefLO2, m.LO2Frequency, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::RefLO2);
// PLL reset appears to realign phases of clock signals
Si5351.ResetPLL(Si5351C::PLL::B);
// 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);
if(excitingPort1) {
data.real_S11 = port1.real();
data.imag_S11 = port1.imag();
data.real_S21 = port2.real();
data.imag_S21 = port2.imag();
} else {
Si5351.Disable(SiChannel::Port1LO2);
Si5351.Disable(SiChannel::Port2LO2);
Si5351.Disable(SiChannel::RefLO2);
data.real_S12 = port1.real();
data.imag_S12 = port1.imag();
data.real_S22 = port2.real();
data.imag_S22 = port2.imag();
}
FPGA::WriteMAX2871Default(Source.GetRegisters());
FPGA::SetNumberOfPoints(1);
FPGA::SetSamplesPerPoint(m.Samples);
// Configure single sweep point
FPGA::WriteSweepConfig(0, !m.SourceHighband, Source.GetRegisters(),
LO1.GetRegisters(), m.attenuator, 0, FPGA::SettlingTime::us20,
FPGA::Samples::SPPRegister, 0,
(FPGA::LowpassFilter) m.SourceHighLowpass);
FPGA::SetWindow((FPGA::Window) m.WindowType);
// Enable/Disable periphery
FPGA::Enable(FPGA::Periphery::SourceChip, m.SourceHighCE);
FPGA::Enable(FPGA::Periphery::SourceRF, m.SourceHighRFEN);
FPGA::Enable(FPGA::Periphery::LO1Chip, m.LO1CE);
FPGA::Enable(FPGA::Periphery::LO1RF, m.LO1RFEN);
FPGA::Enable(FPGA::Periphery::Amplifier, m.AmplifierEN);
FPGA::Enable(FPGA::Periphery::Port1Mixer, m.Port1EN);
FPGA::Enable(FPGA::Periphery::Port2Mixer, m.Port2EN);
FPGA::Enable(FPGA::Periphery::RefMixer, m.RefEN);
FPGA::Enable(FPGA::Periphery::ExcitePort1, m.PortSwitch == 0);
FPGA::Enable(FPGA::Periphery::ExcitePort2, m.PortSwitch == 1);
FPGA::StartSweep();
return true;
}
bool VNA::GetTemps(uint8_t *source, uint8_t *lo) {
FPGA::SetMode(FPGA::Mode::SourcePLL);
*source = Source.GetTemp();
FPGA::SetMode(FPGA::Mode::LOPLL);
*lo = LO1.GetTemp();
FPGA::SetMode(FPGA::Mode::FPGA);
return true;
}
void VNA::fillDeviceInfo(Protocol::DeviceInfo *info) {
// read PLL temperatures
uint8_t tempSource, tempLO;
VNA::GetTemps(&tempSource, &tempLO);
LOG_INFO("PLL temperatures: %u/%u", tempSource, tempLO);
// Read ADC min/max
auto limits = FPGA::GetADCLimits();
LOG_INFO("ADC limits: P1: %d/%d P2: %d/%d R: %d/%d",
limits.P1min, limits.P1max, limits.P2min, limits.P2max,
limits.Rmin, limits.Rmax);
#define ADC_LIMIT 30000
// Set VNA related member of info struct
if(limits.P1min < -ADC_LIMIT || limits.P1max > ADC_LIMIT
|| limits.P2min < -ADC_LIMIT || limits.P2max > ADC_LIMIT
|| limits.Rmin < -ADC_LIMIT || limits.Rmax > ADC_LIMIT) {
info->ADC_overload = true;
// 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 {
info->ADC_overload = false;
// only one port active, point is complete after every measurement
pointComplete = true;
}
auto status = FPGA::GetStatus();
info->LO1_locked = (status & (int) FPGA::Interrupt::LO1Unlock) ? 0 : 1;
info->source_locked = (status & (int) FPGA::Interrupt::SourceUnlock) ? 0 : 1;
info->extRefAvailable = Ref::available();
info->extRefInUse = extRefInUse;
info->temperatures.LO1 = tempLO;
info->temperatures.source = tempSource;
info->temperatures.MCU = 0;
FPGA::ResetADCLimits();
}
bool VNA::Ref::available() {
return Si5351.ExtCLKAvailable();
}
bool VNA::Ref::applySettings(Protocol::ReferenceSettings s) {
if(extOutFreq != s.ExtRefOuputFreq) {
extOutFreq = s.ExtRefOuputFreq;
if(extOutFreq == 0) {
Si5351.Disable(SiChannel::ReferenceOut);
LOG_INFO("External reference output disabled");
} else {
Si5351.SetCLK(SiChannel::ReferenceOut, extOutFreq, Si5351C::PLL::A);
Si5351.Enable(SiChannel::ReferenceOut);
LOG_INFO("External reference output set to %luHz", extOutFreq);
if(pointComplete) {
if (sweepCallback) {
sweepCallback(data);
}
}
bool useExternal = s.UseExternalRef;
if (s.AutomaticSwitch) {
useExternal = Ref::available();
}
if(useExternal != extRefInUse) {
// switch between internal and external reference
extRefInUse = useExternal;
if(extRefInUse) {
if(!Ref::available()) {
LOG_WARN("Forced switch to external reference but no signal detected");
}
Si5351.ConfigureCLKIn(10000000);
Si5351.SetPLL(Si5351C::PLL::A, 800000000, Si5351C::PLLSource::CLKIN);
Si5351.SetPLL(Si5351C::PLL::B, 800000000, Si5351C::PLLSource::CLKIN);
LOG_INFO("Switched to external reference");
FPGA::Enable(FPGA::Periphery::ExtRefLED);
} else {
Si5351.SetPLL(Si5351C::PLL::A, 800000000, Si5351C::PLLSource::XTAL);
Si5351.SetPLL(Si5351C::PLL::B, 800000000, Si5351C::PLLSource::XTAL);
LOG_INFO("Switched to internal reference");
FPGA::Disable(FPGA::Periphery::ExtRefLED);
}
}
constexpr uint32_t lock_timeout = 10;
uint32_t start = HAL_GetTick();
while(!Si5351.Locked(Si5351C::PLL::A) || !Si5351.Locked(Si5351C::PLL::A)) {
if(HAL_GetTick() - start > lock_timeout) {
LOG_ERR("Clock distributor PLLs failed to lock");
return false;
pointCnt++;
if (pointCnt >= settings.points) {
// reached end of sweep, start again
pointCnt = 0;
IFTableIndexCnt = 0;
// request to trigger work function
return true;
}
}
return false;
}
bool VNA::ConfigureGenerator(Protocol::GeneratorSettings g) {
if(g.activePort == 0) {
// both ports disabled, no need to configure PLLs
SetIdle();
return true;
}
Protocol::ManualControl m;
// LOs not required
m.LO1CE = 0;
m.LO1Frequency = 1000000000;
m.LO1RFEN = 0;
m.LO1RFEN = 0;
m.LO2EN = 0;
m.LO2Frequency = 60000000;
m.Port1EN = 0;
m.Port2EN = 0;
m.RefEN = 0;
m.Samples = 131072;
m.WindowType = (int) FPGA::Window::None;
// Select correct source
if(g.frequency < BandSwitchFrequency) {
m.SourceLowEN = 1;
m.SourceLowFrequency = g.frequency;
m.SourceHighCE = 0;
m.SourceHighRFEN = 0;
m.SourceHighFrequency = BandSwitchFrequency;
m.SourceHighLowpass = (int) FPGA::LowpassFilter::M947;
m.SourceHighPower = (int) MAX2871::Power::n4dbm;
m.SourceHighband = false;
} else {
m.SourceLowEN = 0;
m.SourceLowFrequency = BandSwitchFrequency;
m.SourceHighCE = 1;
m.SourceHighRFEN = 1;
m.SourceHighFrequency = g.frequency;
if(g.frequency < 900000000UL) {
m.SourceHighLowpass = (int) FPGA::LowpassFilter::M947;
} else if(g.frequency < 1800000000UL) {
m.SourceHighLowpass = (int) FPGA::LowpassFilter::M1880;
} else if(g.frequency < 3500000000UL) {
m.SourceHighLowpass = (int) FPGA::LowpassFilter::M3500;
} else {
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)
m.SourceHighPower = (int) MAX2871::Power::n4dbm;
m.SourceLowPower = (int) Si5351C::DriveStrength::mA2;
g.cdbm_level += 1000;
}
// calculate required attenuation
uint16_t attval = -g.cdbm_level / 25;
if(attval > 127) {
attval = 127;
}
m.attenuator = attval;
return ConfigureManual(m, nullptr);
void VNA::Work() {
// end of sweep
HW::Ref::update();
// Compile info packet
Protocol::PacketInfo packet;
packet.type = Protocol::PacketType::DeviceInfo;
packet.info.FPGA_configured = 1;
packet.info.FW_major = FW_MAJOR;
packet.info.FW_minor = FW_MINOR;
packet.info.HW_Revision = HW_REVISION;
HW::fillDeviceInfo(&packet.info);
Communication::Send(packet);
FPGA::ResetADCLimits();
// Start next sweep
FPGA::StartSweep();
}
void VNA::SetIdle() {
FPGA::AbortSweep();
FPGA::SetMode(FPGA::Mode::FPGA);
FPGA::Enable(FPGA::Periphery::SourceChip, false);
FPGA::Enable(FPGA::Periphery::SourceRF, false);
FPGA::Enable(FPGA::Periphery::LO1Chip, false);
FPGA::Enable(FPGA::Periphery::LO1RF, false);
FPGA::Enable(FPGA::Periphery::Amplifier, false);
FPGA::Enable(FPGA::Periphery::Port1Mixer, false);
FPGA::Enable(FPGA::Periphery::Port2Mixer, false);
FPGA::Enable(FPGA::Periphery::RefMixer, false);
void VNA::SweepHalted() {
if(!active) {
return;
}
LOG_DEBUG("Halted before point %d", pointCnt);
// Check if IF table has entry at this point
// if (IFTable[IFTableIndexCnt].pointCnt == pointCnt) {
// LOG_DEBUG("Shifting IF to %lu at point %u",
// IFTable[IFTableIndexCnt].IF1, pointCnt);
// Si5351.WriteRawCLKConfig(1, IFTable[IFTableIndexCnt].clkconfig);
// Si5351.WriteRawCLKConfig(4, IFTable[IFTableIndexCnt].clkconfig);
// Si5351.WriteRawCLKConfig(5, IFTable[IFTableIndexCnt].clkconfig);
// Si5351.ResetPLL(Si5351C::PLL::B);
// IFTableIndexCnt++;
// }
uint64_t frequency = settings.f_start
+ (settings.f_stop - settings.f_start) * pointCnt
/ (settings.points - 1);
if (frequency < BandSwitchFrequency) {
// need the Si5351 as Source
Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::B,
Si5351C::DriveStrength::mA2);
if (pointCnt == 0) {
// First point in sweep, enable CLK
Si5351.Enable(SiChannel::LowbandSource);
FPGA::Disable(FPGA::Periphery::SourceRF);
}
} else {
// first sweep point in highband is also halted, disable lowband source
Si5351.Disable(SiChannel::LowbandSource);
FPGA::Enable(FPGA::Periphery::SourceRF);
}
FPGA::ResumeHaltedSweep();
}
void VNA::Stop() {
active = false;
FPGA::AbortSweep();
}