WIP: rework 2.LO + add dwell time

This commit is contained in:
Jan Käberich 2025-01-02 19:16:53 +01:00
parent a2abc0c2af
commit 24314e2361
33 changed files with 483 additions and 190 deletions

View file

@ -268,9 +268,7 @@ inline void App_Process() {
{
Protocol::PacketInfo send;
send.type = Protocol::PacketType::DeviceConfiguration;
send.deviceConfig.V1.IF1 = HW::getIF1();
send.deviceConfig.V1.ADCprescaler = HW::getADCPrescaler();
send.deviceConfig.V1.DFTphaseInc = HW::getDFTPhaseInc();
send.deviceConfig = HW::getDeviceConfig();
Communication::Send(send);
}
break;
@ -334,7 +332,50 @@ inline void App_Process() {
}
}
#include "HW_HAL.hpp"
void App_Start() {
App_Init();
// uint32_t LO2_1 = 61750000;
// uint32_t LO2_2 = 61752000;
//
// HWHAL::Si5351.Enable(HWHAL::SiChannel::Port1LO2);
// HWHAL::Si5351.Enable(HWHAL::SiChannel::Port2LO2);
// HWHAL::Si5351.Enable(HWHAL::SiChannel::RefLO2);
//
//// HWHAL::Si5351.Disable(HWHAL::SiChannel::Port1LO2);
//// HWHAL::Si5351.Disable(HWHAL::SiChannel::Port2LO2);
//// HWHAL::Si5351.Disable(HWHAL::SiChannel::RefLO2);
//
//// FPGA::Enable(FPGA::Periphery::Port1Mixer);
//// FPGA::Enable(FPGA::Periphery::Port2Mixer);
//// FPGA::Enable(FPGA::Periphery::RefMixer);
//
// uint32_t i=64000000;
// HWHAL::Si5351.SetCLK(HWHAL::SiChannel::Port1LO2, i, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// HWHAL::Si5351.SetCLK(HWHAL::SiChannel::Port2LO2, i, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// HWHAL::Si5351.SetCLK(HWHAL::SiChannel::RefLO2, i, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// HWHAL::Si5351.ResetPLL(Si5351C::PLL::B);
// while(1) {
// for(i=61000000;i<62000000;i++) {
// LOG_INFO("Setting LO2=%lu", i);
// HWHAL::Si5351.SetPLL(Si5351C::PLL::B, i*13, Si5351C::PLLSource::XTAL);
//// HWHAL::Si5351.Disable(HWHAL::SiChannel::Port1LO2);
//// HWHAL::Si5351.Disable(HWHAL::SiChannel::Port2LO2);
//// HWHAL::Si5351.Disable(HWHAL::SiChannel::RefLO2);
//// HWHAL::Si5351.SetCLK(HWHAL::SiChannel::Port1LO2, i, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
//// HWHAL::Si5351.SetCLK(HWHAL::SiChannel::Port2LO2, i, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
//// HWHAL::Si5351.SetCLK(HWHAL::SiChannel::RefLO2, i, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
//
//// HWHAL::Si5351.Enable(HWHAL::SiChannel::Port1LO2);
//// HWHAL::Si5351.Enable(HWHAL::SiChannel::Port2LO2);
//// HWHAL::Si5351.Enable(HWHAL::SiChannel::RefLO2);
//// HWHAL::Si5351.ResetPLL(Si5351C::PLL::B);
//// HWHAL::Si5351.WaitForLock(Si5351C::PLL::B, 10);
// vTaskDelay(1);
// }
// }
App_Process();
}

View file

@ -10,7 +10,7 @@ using namespace PacketConstants;
namespace Protocol {
static constexpr uint16_t Version = 13;
static constexpr uint16_t Version = 14;
#pragma pack(push, 1)
@ -180,6 +180,7 @@ using SweepSettings = struct _sweepSettings {
uint16_t unused2:1;
int16_t cdbm_excitation_stop; // in 1/100 dbm
uint16_t dwell_time; // in us
};
using ReferenceSettings = struct _referenceSettings {
@ -215,6 +216,8 @@ using DeviceInfo = struct _deviceInfo {
uint8_t limits_maxAmplitudePoints;
uint64_t limits_maxFreqHarmonic;
uint8_t num_ports;
uint16_t limits_minDwellTime;
uint16_t limits_maxDwellTime;
};
using DeviceStatus = struct _deviceStatus {

View file

@ -132,6 +132,17 @@ void FPGA::SetSamplesPerPoint(uint32_t nsamples) {
WriteRegister(Reg::SamplesPerPoint, nsamples);
}
void FPGA::SetSettlingTime(uint16_t us) {
// register is in multiples of 1/102.4 MHz
uint32_t value = (uint32_t) us * 512 / 5;
constexpr uint32_t maxval = 0xFFFFF;
if(value > maxval) {
value = maxval;
}
WriteRegister(Reg::SettlingTimeLow, value & 0xFFFF);
WriteRegister(Reg::SettlingTimeHigh, value >> 16);
}
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize, bool syncMaster) {
uint16_t value = 0x0000;
value |= (uint16_t) (stages & 0x07) << 13;
@ -199,7 +210,7 @@ void FPGA::WriteMAX2871Default(uint32_t *DefaultRegs) {
}
void FPGA::WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceRegs, uint32_t *LORegs,
uint8_t attenuation, uint64_t frequency, SettlingTime settling, Samples samples, bool halt, LowpassFilter filter) {
uint8_t attenuation, uint64_t frequency, Samples samples, bool halt, LowpassFilter filter) {
uint16_t send[7];
// select which point this sweep config is for
send[0] = pointnum & 0x1FFF;
@ -222,7 +233,12 @@ void FPGA::WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceReg
if (halt) {
send[1] |= 0x8000;
}
send[1] |= (int) settling << 13;
if(LO_N & 0x40) {
send[1] |= 0x4000;
}
if(Source_N & 0x40) {
send[1] |= 0x2000;
}
send[1] |= (int) samples << 10;
if(filter == LowpassFilter::Auto) {
// Select source LP filter
@ -239,13 +255,13 @@ void FPGA::WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceReg
send[1] |= (int) filter << 8;
}
send[2] = (LO_M & 0x000F) << 12 | LO_FRAC;
send[3] = LO_DIV_A << 13 | LO_VCO << 7 | LO_N << 1;
send[3] = LO_DIV_A << 13 | LO_VCO << 7 | (LO_N & 0x3F) << 1;
if (lowband) {
send[3] |= 0x0001;
}
send[4] = Source_Power << 14 | (uint16_t) attenuation << 7 | Source_M >> 5;
send[5] = (Source_M & 0x001F) << 11 | Source_FRAC >> 1;
send[6] = (Source_FRAC & 0x0001) << 15 | Source_DIV_A << 12 | Source_VCO << 6 | Source_N;
send[6] = (Source_FRAC & 0x0001) << 15 | Source_DIV_A << 12 | Source_VCO << 6 | (Source_N & 0x3F);
SwitchBytes(send[0]);
SwitchBytes(send[1]);
SwitchBytes(send[2]);

View file

@ -29,6 +29,8 @@ enum class Reg {
MAX2871Def4MSB = 0x0F,
DFTFirstBin = 0x12,
DFTFreqSpacing = 0x13,
SettlingTimeLow = 0x14,
SettlingTimeHigh = 0x15,
};
using SamplingResult = struct _samplingresult {
@ -82,13 +84,6 @@ enum class LowpassFilter {
Auto = 0xFF,
};
enum class SettlingTime {
us20 = 0x00,
us60 = 0x01,
us180 = 0x02,
us540 = 0x03,
};
enum class Samples {
SPPRegister = 0x00,
S96 = 0x01,
@ -114,6 +109,7 @@ bool Init(HaltedCallback cb = nullptr);
void WriteRegister(FPGA::Reg reg, uint16_t value);
void SetNumberOfPoints(uint16_t npoints);
void SetSamplesPerPoint(uint32_t nsamples);
void SetSettlingTime(uint16_t us);
void SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize = false, bool syncMaster = false);
void Enable(Periphery p, bool enable = true);
void Disable(Periphery p);
@ -124,7 +120,7 @@ void DisableInterrupt(Interrupt i);
void DisableAllInterrupts();
void WriteMAX2871Default(uint32_t *DefaultRegs);
void WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceRegs, uint32_t *LORegs,
uint8_t attenuation, uint64_t frequency, SettlingTime settling, Samples samples, bool halt = false, LowpassFilter filter = LowpassFilter::Auto);
uint8_t attenuation, uint64_t frequency, Samples samples, bool halt = false, LowpassFilter filter = LowpassFilter::Auto);
using ReadCallback = void(*)(const SamplingResult &result);
bool InitiateSampleRead(ReadCallback cb);
void SetupDFT(uint32_t f_firstBin, uint32_t f_binSpacing);

View file

@ -70,7 +70,7 @@ bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) {
FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3);
FreqPLL[(int) pll] = frequency;
LOG_INFO("Setting PLL %c to %luHz", pll==PLL::A ? 'A' : 'B', frequency);
LOG_DEBUG("Setting PLL %c to %luHz", pll==PLL::A ? 'A' : 'B', frequency);
return WritePLLConfig(c, pll);
}

View file

@ -9,6 +9,11 @@
using namespace HWHAL;
void Generator::Setup(Protocol::GeneratorSettings g) {
// Disable 2.LO
Si5351.Disable(SiChannel::Port1LO2);
Si5351.Disable(SiChannel::Port2LO2);
Si5351.Disable(SiChannel::RefLO2);
HW::SetMode(HW::Mode::Generator);
if(g.activePort == 0) {
// both ports disabled, no need to configure PLLs

View file

@ -115,21 +115,25 @@ bool HW::Init() {
// Both MAX2871 get a 100MHz reference
// Si5351.SetBypass(SiChannel::Source, Si5351C::PLLSource::XTAL);
Si5351.SetCLK(SiChannel::Source, HW::PLLRef, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::Source, HW::PLLRef, Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
Si5351.Enable(SiChannel::Source);
// Si5351.SetBypass(SiChannel::LO1, Si5351C::PLLSource::XTAL);
Si5351.SetCLK(SiChannel::LO1, HW::PLLRef, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::LO1, HW::PLLRef, Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
Si5351.Enable(SiChannel::LO1);
// 16MHz FPGA clock
Si5351.SetCLK(SiChannel::FPGA, HW::FPGAClkInFrequency, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::FPGA, HW::FPGAClkInFrequency, Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
Si5351.Enable(SiChannel::FPGA);
// Generate second LO with Si5351
Si5351.SetCLK(SiChannel::Port1LO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// The 2.LO frequency is only set up here once. The frequencies chosen for DefaultLO2 and PLL B must
// have an integer divisor. When changing the 2.LO frequency after this point, the PLL B frequency is
// changed instead of modifying the clock output dividers. Otherwise, phase reversal may happen
// intermittently at one or multiple 2.LO outputs. See also https://github.com/jankae/LibreVNA/issues/280
Si5351.SetCLK(SiChannel::Port1LO2, DefaultLO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port1LO2);
Si5351.SetCLK(SiChannel::Port2LO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::Port2LO2, DefaultLO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port2LO2);
Si5351.SetCLK(SiChannel::RefLO2, IF1 - IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::RefLO2, DefaultLO2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::RefLO2);
// PLL reset appears to realign phases of clock signals
@ -152,6 +156,9 @@ bool HW::Init() {
// Set phase increment according to
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, DFTphaseInc);
// Set default settling time
FPGA::SetSettlingTime(HW::DefaultDwellTime);
Exti::SetCallback(FPGA_INTR_GPIO_Port, FPGA_INTR_Pin, Exti::EdgeType::Rising, Exti::Pull::Down, FPGA_Interrupt);
// Initialize PLLs and build VCO maps
@ -413,6 +420,14 @@ void HW::Ref::update() {
}
}
Si5351C::PLLSource HW::Ref::getSource() {
if(extRefInUse) {
return Si5351C::PLLSource::CLKIN;
} else {
return Si5351C::PLLSource::XTAL;
}
}
void HW::setAcquisitionFrequencies(Protocol::DeviceConfig s) {
IF1 = s.V1.IF1;
ADCprescaler = s.V1.ADCprescaler;
@ -422,6 +437,14 @@ void HW::setAcquisitionFrequencies(Protocol::DeviceConfig s) {
ADCsamplerate = ADCrate;
}
Protocol::DeviceConfig HW::getDeviceConfig() {
Protocol::DeviceConfig s;
s.V1.ADCprescaler = ADCprescaler;
s.V1.DFTphaseInc = DFTphaseInc;
s.V1.IF1 = IF1;
return s;
}
uint32_t HW::getIF1() {
return IF1;
}

View file

@ -32,7 +32,6 @@ static constexpr uint32_t TCXOFrequency = 26000000;
static constexpr uint32_t ExtRefInFrequency = 10000000;
static constexpr uint32_t ExtRefOut1Frequency = 10000000;
static constexpr uint32_t ExtRefOut2Frequency = 10000000;
static constexpr uint32_t SI5351CPLLAlignedFrequency = 832000000;
static constexpr uint32_t SI5351CPLLConstantFrequency = 800000000;
static constexpr uint32_t FPGAClkInFrequency = 16000000;
static constexpr uint32_t DefaultADCSamplerate = 800000;
@ -41,8 +40,12 @@ static constexpr uint32_t DefaultIF2 = 250000;
static constexpr uint32_t LO1_minFreq = 25000000;
static constexpr uint32_t MaxSamples = 130944;
static constexpr uint32_t MinSamples = 16;
static constexpr uint32_t PLLRef = 104000000;
static constexpr uint32_t PLLRef = 100000000;
static constexpr uint32_t BandSwitchFrequency = 25000000;
static constexpr uint32_t DefaultLO2 = DefaultIF1 - DefaultIF2;
static constexpr uint8_t LO2Multiplier = 13;
static constexpr uint32_t SI5351CPLLAlignedFrequency = DefaultLO2 * LO2Multiplier;
static constexpr uint16_t DefaultDwellTime = 60;
static constexpr uint8_t DefaultADCprescaler = FPGA::Clockrate / DefaultADCSamplerate;
static_assert(DefaultADCprescaler * DefaultADCSamplerate == FPGA::Clockrate, "ADCSamplerate can not be reached exactly");
@ -84,6 +87,8 @@ static constexpr Protocol::DeviceInfo Info = {
.limits_maxAmplitudePoints = Cal::maxPoints,
.limits_maxFreqHarmonic = 18000000000,
.num_ports = 2,
.limits_minDwellTime = 0,
.limits_maxDwellTime = 10239,
};
enum class Mode {
@ -127,10 +132,12 @@ namespace Ref {
// reference won't change until update is called
void set(Protocol::ReferenceSettings s);
void update();
Si5351C::PLLSource getSource();
}
// Acquisition frequency settings
void setAcquisitionFrequencies(Protocol::DeviceConfig s);
Protocol::DeviceConfig getDeviceConfig();
uint32_t getIF1();
uint32_t getIF2();
uint32_t getADCRate();

View file

@ -32,13 +32,12 @@ void Manual::Setup(Protocol::ManualControl m) {
// Configure LO2
if(m.V1.LO2EN) {
// Generate second LO with Si5351
Si5351.SetCLK(SiChannel::Port1LO2, m.V1.LO2Frequency, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port1LO2);
Si5351.SetCLK(SiChannel::Port2LO2, m.V1.LO2Frequency, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Port2LO2);
Si5351.SetCLK(SiChannel::RefLO2, m.V1.LO2Frequency, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::RefLO2);
Si5351.SetPLL(Si5351C::PLL::B, m.V1.LO2Frequency*HW::LO2Multiplier, HW::Ref::getSource());
// PLL reset appears to realign phases of clock signals
Si5351.ResetPLL(Si5351C::PLL::B);
} else {
@ -54,8 +53,7 @@ void Manual::Setup(Protocol::ManualControl m) {
// Configure single sweep point
FPGA::WriteSweepConfig(0, !m.V1.SourceHighband, Source.GetRegisters(),
LO1.GetRegisters(), m.V1.attenuator, 0, FPGA::SettlingTime::us60,
FPGA::Samples::SPPRegister, 0,
LO1.GetRegisters(), m.V1.attenuator, 0, FPGA::Samples::SPPRegister, 0,
(FPGA::LowpassFilter) m.V1.SourceHighLowpass);
FPGA::SetWindow((FPGA::Window) m.V1.WindowType);

View file

@ -158,8 +158,7 @@ static void StartNextSample() {
// only adjust LO2 PLL if necessary (if the deviation is significantly less than the RBW it does not matter)
if((uint32_t) abs(LO2freq - lastLO2) > actualRBW / 100) {
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetPLL(Si5351C::PLL::B, LO2freq*HW::LO2Multiplier, HW::Ref::getSource());
lastLO2 = LO2freq;
}
if (s.UseDFT) {
@ -175,7 +174,7 @@ static void StartNextSample() {
// Configure the sampling in the FPGA
FPGA::WriteSweepConfig(0, trackingLowband, Source.GetRegisters(), LO1.GetRegisters(), attenuator,
trackingFreq, FPGA::SettlingTime::us60, FPGA::Samples::SPPRegister, 0,
trackingFreq, FPGA::Samples::SPPRegister, 0,
FPGA::LowpassFilter::Auto);
if(firstSample && (signalIDstep == 0)) {

View file

@ -132,6 +132,8 @@ bool VNA::Setup(Protocol::SweepSettings s) {
// has to be one less than actual number of samples
FPGA::SetSamplesPerPoint(samplesPerPoint);
FPGA::SetSettlingTime(s.dwell_time);
// reset unlevel flag if it was set from a previous sweep/mode
HW::SetOutputUnlevel(false);
// Start with average level
@ -161,9 +163,10 @@ bool VNA::Setup(Protocol::SweepSettings s) {
FPGA::WriteMAX2871Default(Source.GetRegisters());
last_LO2 = HW::getIF1() - HW::getIF2();
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.Enable(SiChannel::Port1LO2);
Si5351.Enable(SiChannel::Port2LO2);
Si5351.Enable(SiChannel::RefLO2);
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
Si5351.ResetPLL(Si5351C::PLL::B);
Si5351.WaitForLock(Si5351C::PLL::B, 10);
@ -193,9 +196,14 @@ bool VNA::Setup(Protocol::SweepSettings s) {
// SetFrequency only manipulates the register content in RAM, no SPI communication is done.
// No mode-switch of FPGA necessary here.
setPLLFrequencies(freq);
if(s.suppressPeaks) {
if(needs2LOshift(freq, last_LO2, actualBandwidth, &last_LO2)) {
uint32_t new_LO2;
auto needs_shift = needs2LOshift(freq, last_LO2, actualBandwidth, &new_LO2);
if(needs_shift) {
if(s.suppressPeaks) {
needs_halt = true;
last_LO2 = new_LO2;
} else {
LOG_WARN("Point at f=%lu%06lu needs an LO shift but the feature is disabled. This will cause a peak.", (uint32_t) (freq/1000000), (uint32_t) (freq%1000000));
}
}
if (last_lowband && !lowband) {
@ -232,7 +240,7 @@ bool VNA::Setup(Protocol::SweepSettings s) {
}
FPGA::WriteSweepConfig(i, lowband, Source.GetRegisters(),
LO1.GetRegisters(), attenuator, freq, FPGA::SettlingTime::us60,
LO1.GetRegisters(), attenuator, freq,
FPGA::Samples::SPPRegister, needs_halt);
last_lowband = lowband;
}
@ -428,9 +436,7 @@ void VNA::SweepHalted() {
// is required to determine the need for a 2.LO shift
setPLLFrequencies(frequency);
if(needs2LOshift(frequency, last_LO2, actualBandwidth, &last_LO2)) {
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.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
Si5351.ResetPLL(Si5351C::PLL::B);
Si5351.WaitForLock(Si5351C::PLL::B, 10);
// PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point