mirror of
https://github.com/jankae/LibreVNA.git
synced 2025-12-06 07:12:10 +01:00
improve PLL fractional divider algorithm
This commit is contained in:
parent
66d5bdd91b
commit
8b44421ea3
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include "algorithm.hpp"
|
||||||
|
|
||||||
#define LOG_LEVEL LOG_LEVEL_INFO
|
#define LOG_LEVEL LOG_LEVEL_INFO
|
||||||
#define LOG_MODULE "SI5351"
|
#define LOG_MODULE "SI5351"
|
||||||
|
|
@ -50,7 +51,7 @@ bool Si5351C::ConfigureCLKIn(uint32_t clkin_freq) {
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) {
|
bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency) {
|
||||||
if (frequency < 600000000 || frequency > 900000000) {
|
if (frequency < 600000000 || frequency > 900000000) {
|
||||||
LOG_ERR("Requested PLL frequency out of range (600-900MHz): %lu", frequency);
|
LOG_ERR("Requested PLL frequency out of range (600-900MHz): %lu", frequency);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -67,14 +68,14 @@ bool Si5351C::SetPLL(PLL pll, uint32_t frequency, PLLSource src) {
|
||||||
LOG_ERR("Calculated divider out of range (15-90)");
|
LOG_ERR("Calculated divider out of range (15-90)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3);
|
FindOptimalDivider(frequency, srcFreq, c.P1, c.P2, c.P3, exactFrequency);
|
||||||
|
|
||||||
FreqPLL[(int) pll] = frequency;
|
FreqPLL[(int) pll] = frequency;
|
||||||
LOG_DEBUG("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);
|
return WritePLLConfig(c, pll);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength, uint32_t PLLFreqOverride) {
|
bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength, uint32_t PLLFreqOverride, bool exactFrequency) {
|
||||||
ClkConfig c;
|
ClkConfig c;
|
||||||
c.DivideBy4 = false;
|
c.DivideBy4 = false;
|
||||||
c.IntegerMode = false;
|
c.IntegerMode = false;
|
||||||
|
|
@ -113,7 +114,7 @@ bool Si5351C::SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStreng
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FindOptimalDivider(pllFreq, frequency * c.RDiv, c.P1, c.P2, c.P3);
|
FindOptimalDivider(pllFreq, frequency * c.RDiv, c.P1, c.P2, c.P3, exactFrequency);
|
||||||
}
|
}
|
||||||
LOG_DEBUG("Setting CLK%d to %luHz", clknum, frequency);
|
LOG_DEBUG("Setting CLK%d to %luHz", clknum, frequency);
|
||||||
return WriteClkConfig(c, clknum);
|
return WriteClkConfig(c, clknum);
|
||||||
|
|
@ -346,13 +347,26 @@ bool Si5351C::ResetPLL(PLL pll) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Si5351C::FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1,
|
void Si5351C::FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1,
|
||||||
uint32_t &P2, uint32_t &P3) {
|
uint32_t &P2, uint32_t &P3, bool exact) {
|
||||||
// see https://www.silabs.com/documents/public/application-notes/AN619.pdf (page 3/6)
|
// see https://www.silabs.com/documents/public/application-notes/AN619.pdf (page 3/6)
|
||||||
uint32_t a = f_pll / f;
|
uint32_t a = f_pll / f;
|
||||||
int32_t f_rem = f_pll - f * a;
|
int32_t f_rem = f_pll - f * a;
|
||||||
// always using the highest modulus divider results in less than 1Hz deviation for all frequencies, that is good enough
|
uint32_t best_c;
|
||||||
uint32_t best_c = (1UL << 20) - 1;
|
uint32_t best_b;
|
||||||
uint32_t best_b = (uint64_t) f_rem * best_c / f;
|
if(exact) {
|
||||||
|
// spend the effort to find the best divider possible
|
||||||
|
Algorithm::RationalApproximation ratio;
|
||||||
|
ratio.num = f_rem;
|
||||||
|
ratio.denom = f;
|
||||||
|
auto approx = Algorithm::BestRationalApproximation(ratio, (1UL << 20) - 1);
|
||||||
|
best_c = approx.denom;
|
||||||
|
best_b = approx.num;
|
||||||
|
} else {
|
||||||
|
// just use the highest denominator possible, this is good enough if no exact frequency is required
|
||||||
|
best_c = (1UL << 20) - 1;
|
||||||
|
best_b = (uint64_t) f_rem * best_c / f;
|
||||||
|
}
|
||||||
|
|
||||||
// convert to Si5351C parameters
|
// convert to Si5351C parameters
|
||||||
uint32_t floor = 128 * best_b / best_c;
|
uint32_t floor = 128 * best_b / best_c;
|
||||||
P1 = 128 * a + floor - 512;
|
P1 = 128 * a + floor - 512;
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ public:
|
||||||
};
|
};
|
||||||
bool Init(uint32_t clkin_freq = 0);
|
bool Init(uint32_t clkin_freq = 0);
|
||||||
bool ConfigureCLKIn(uint32_t clkin_freq);
|
bool ConfigureCLKIn(uint32_t clkin_freq);
|
||||||
bool SetPLL(PLL pll, uint32_t frequency, PLLSource src);
|
bool SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency=true);
|
||||||
bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, uint32_t PLLFreqOverride = 0);
|
bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, uint32_t PLLFreqOverride = 0, bool exactFrequency=true);
|
||||||
bool SetBypass(uint8_t clknum, PLLSource source, DriveStrength strength = DriveStrength::mA2);
|
bool SetBypass(uint8_t clknum, PLLSource source, DriveStrength strength = DriveStrength::mA2);
|
||||||
bool SetCLKtoXTAL(uint8_t clknum);
|
bool SetCLKtoXTAL(uint8_t clknum);
|
||||||
bool SetCLKToCLKIN(uint8_t clknum);
|
bool SetCLKToCLKIN(uint8_t clknum);
|
||||||
|
|
@ -48,7 +48,7 @@ public:
|
||||||
bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config);
|
bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config);
|
||||||
bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config);
|
bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config);
|
||||||
private:
|
private:
|
||||||
void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3);
|
void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3, bool exact);
|
||||||
enum class Reg : uint8_t {
|
enum class Reg : uint8_t {
|
||||||
DeviceStatus = 0,
|
DeviceStatus = 0,
|
||||||
InterruptStatusSticky = 1,
|
InterruptStatusSticky = 1,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "stm.hpp"
|
#include "stm.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float ratio, uint32_t max_denom) {
|
Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float ratio, uint32_t max_denom) {
|
||||||
RationalApproximation result;
|
RationalApproximation result;
|
||||||
|
|
@ -42,3 +43,47 @@ Algorithm::RationalApproximation Algorithm::BestRationalApproximation(float rati
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t gcd(uint32_t u, uint32_t v) {
|
||||||
|
if(u==0) {
|
||||||
|
return v;
|
||||||
|
} else if(v==0) {
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t i = __builtin_ctz(u);
|
||||||
|
u >>= i;
|
||||||
|
uint8_t j = __builtin_ctz(v);
|
||||||
|
v >>= j;
|
||||||
|
|
||||||
|
uint8_t k = i < j ? i : j;
|
||||||
|
while(true) {
|
||||||
|
if(u > v) {
|
||||||
|
std::swap(u, v);
|
||||||
|
}
|
||||||
|
v -= u;
|
||||||
|
if(v==0) {
|
||||||
|
return u << k;
|
||||||
|
}
|
||||||
|
v >>= __builtin_ctz(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Algorithm::RationalApproximation Algorithm::BestRationalApproximation(
|
||||||
|
RationalApproximation ratio, uint32_t max_denom) {
|
||||||
|
if(ratio.denom <= max_denom) {
|
||||||
|
// nothing to do, we can just return the ratio as it is
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
// Try to simplify the ratio.
|
||||||
|
// Find greatest common divider
|
||||||
|
uint32_t div = gcd(ratio.num, ratio.denom);
|
||||||
|
ratio.num /= div;
|
||||||
|
ratio.denom /= div;
|
||||||
|
if(ratio.denom <= max_denom) {
|
||||||
|
// small enough now, can use as is
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
// no good, we need to approximate
|
||||||
|
return Algorithm::BestRationalApproximation((float) ratio.num / ratio.denom, max_denom);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using RationalApproximation = struct _rationalapproximation {
|
||||||
uint32_t denom;
|
uint32_t denom;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RationalApproximation BestRationalApproximation(RationalApproximation ratio, uint32_t max_denom);
|
||||||
RationalApproximation BestRationalApproximation(float ratio, uint32_t max_denom);
|
RationalApproximation BestRationalApproximation(float ratio, uint32_t max_denom);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,10 @@ bool MAX2871::SetFrequency(uint64_t f) {
|
||||||
LOG_DEBUG("Looking for best fractional match");
|
LOG_DEBUG("Looking for best fractional match");
|
||||||
float fraction = (float) rem_f / f_PFD;
|
float fraction = (float) rem_f / f_PFD;
|
||||||
|
|
||||||
auto approx = Algorithm::BestRationalApproximation(fraction, 4095);
|
Algorithm::RationalApproximation ratio;
|
||||||
|
ratio.num = rem_f;
|
||||||
|
ratio.denom = f_PFD;
|
||||||
|
auto approx = Algorithm::BestRationalApproximation(ratio, 4095);
|
||||||
|
|
||||||
if (approx.denom == approx.num) {
|
if (approx.denom == approx.num) {
|
||||||
// got an impossible result due to floating point limitations(?)
|
// got an impossible result due to floating point limitations(?)
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ static void StartNextSample() {
|
||||||
}
|
}
|
||||||
attenuator = amplitude.attenuator;
|
attenuator = amplitude.attenuator;
|
||||||
if(trackingFreq < HW::BandSwitchFrequency) {
|
if(trackingFreq < HW::BandSwitchFrequency) {
|
||||||
Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::A, amplitude.lowBandPower);
|
Si5351.SetCLK(SiChannel::LowbandSource, trackingFreq, Si5351C::PLL::A, amplitude.lowBandPower, false);
|
||||||
FPGA::Disable(FPGA::Periphery::SourceChip);
|
FPGA::Disable(FPGA::Periphery::SourceChip);
|
||||||
FPGA::Disable(FPGA::Periphery::SourceRF);
|
FPGA::Disable(FPGA::Periphery::SourceRF);
|
||||||
trackingLowband = true;
|
trackingLowband = true;
|
||||||
|
|
@ -198,8 +198,8 @@ static void StartNextSample() {
|
||||||
// only adjust LO2 PLL if necessary (if the deviation is significantly less than the RBW it does not matter)
|
// 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) {
|
if((uint32_t) abs(LO2freq - lastLO2) > actualRBW / 100) {
|
||||||
// Si5351.SetPLL(Si5351C::PLL::B, LO2freq*HW::LO2Multiplier, HW::Ref::getSource());
|
// Si5351.SetPLL(Si5351C::PLL::B, LO2freq*HW::LO2Multiplier, HW::Ref::getSource());
|
||||||
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false);
|
||||||
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
|
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2, false);
|
||||||
lastLO2 = LO2freq;
|
lastLO2 = LO2freq;
|
||||||
}
|
}
|
||||||
if (s.UseDFT) {
|
if (s.UseDFT) {
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ bool VNA::Setup(Protocol::SweepSettings s) {
|
||||||
Si5351.Enable(SiChannel::Port1LO2);
|
Si5351.Enable(SiChannel::Port1LO2);
|
||||||
Si5351.Enable(SiChannel::Port2LO2);
|
Si5351.Enable(SiChannel::Port2LO2);
|
||||||
Si5351.Enable(SiChannel::RefLO2);
|
Si5351.Enable(SiChannel::RefLO2);
|
||||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
|
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false);
|
||||||
Si5351.ResetPLL(Si5351C::PLL::B);
|
Si5351.ResetPLL(Si5351C::PLL::B);
|
||||||
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
||||||
|
|
||||||
|
|
@ -458,7 +458,7 @@ void VNA::SweepHalted() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// need the Si5351 as Source
|
// need the Si5351 as Source
|
||||||
bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::A, driveStrength);
|
bool freqSuccess = Si5351.SetCLK(SiChannel::LowbandSource, frequency, Si5351C::PLL::A, driveStrength, false);
|
||||||
static bool lowbandDisabled = false;
|
static bool lowbandDisabled = false;
|
||||||
if (pointCnt == 0) {
|
if (pointCnt == 0) {
|
||||||
// First point in sweep, switch to correct source
|
// First point in sweep, switch to correct source
|
||||||
|
|
@ -499,7 +499,7 @@ void VNA::SweepHalted() {
|
||||||
Si5351.SetCLK(SiChannel::Source, PLLRefFreqs[sourceRefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
Si5351.SetCLK(SiChannel::Source, PLLRefFreqs[sourceRefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
||||||
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
||||||
last_LO2 = HW::getIF1() - HW::getIF2();
|
last_LO2 = HW::getIF1() - HW::getIF2();
|
||||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
|
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false);
|
||||||
Si5351.ResetPLL(Si5351C::PLL::B);
|
Si5351.ResetPLL(Si5351C::PLL::B);
|
||||||
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
||||||
HAL_Delay(2);
|
HAL_Delay(2);
|
||||||
|
|
@ -514,7 +514,7 @@ void VNA::SweepHalted() {
|
||||||
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
Si5351.SetCLK(SiChannel::LO1, PLLRefFreqs[LO1RefIndex], Si5351C::PLL::A, Si5351C::DriveStrength::mA8);
|
||||||
}
|
}
|
||||||
if(needs2LOshift(frequency, last_LO2, actualBandwidth, &last_LO2)) {
|
if(needs2LOshift(frequency, last_LO2, actualBandwidth, &last_LO2)) {
|
||||||
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource());
|
Si5351.SetPLL(Si5351C::PLL::B, last_LO2*HW::LO2Multiplier, HW::Ref::getSource(), false);
|
||||||
Si5351.ResetPLL(Si5351C::PLL::B);
|
Si5351.ResetPLL(Si5351C::PLL::B);
|
||||||
Si5351.WaitForLock(Si5351C::PLL::B, 10);
|
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
|
// PLL reset causes the 2.LO to turn off briefly and then ramp on back, needs delay before next point
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue