mitigation for peaks caused by limited fractional divider in PLLs

This commit is contained in:
Jan Käberich 2020-09-20 10:13:06 +02:00
parent fc3ce7a828
commit 57b4ebfb26
23 changed files with 654 additions and 274 deletions

View file

@ -176,6 +176,7 @@ static Protocol::SweepSettings DecodeSweepSettings(uint8_t *buf) {
e.get<int16_t>(d.cdbm_excitation);
d.excitePort1 = e.getBits(1);
d.excitePort2 = e.getBits(1);
d.suppressPeaks = e.getBits(1);
return d;
}
static int16_t EncodeSweepSettings(Protocol::SweepSettings d, uint8_t *buf,
@ -188,6 +189,7 @@ static int16_t EncodeSweepSettings(Protocol::SweepSettings d, uint8_t *buf,
e.add<int16_t>(d.cdbm_excitation);
e.addBits(d.excitePort1, 1);
e.addBits(d.excitePort2, 1);
e.addBits(d.suppressPeaks, 1);
return e.getSize();
}

View file

@ -23,6 +23,7 @@ using SweepSettings = struct _sweepSettings {
int16_t cdbm_excitation; // in 1/100 dbm
uint8_t excitePort1:1;
uint8_t excitePort2:1;
uint8_t suppressPeaks:1;
};
using ReferenceSettings = struct _referenceSettings {

View file

@ -3,6 +3,7 @@
#include "usbd_core.h"
USBD_HandleTypeDef hUsbDeviceFS;
extern PCD_HandleTypeDef hpcd_USB_FS;
#define EP_DATA_IN_ADDRESS 0x81
#define EP_DATA_OUT_ADDRESS 0x01
@ -186,7 +187,9 @@ void usb_init(usbd_callback_t callback) {
USBD_Init(&hUsbDeviceFS, &FS_Desc, 0);
USBD_RegisterClass(&hUsbDeviceFS, &USBD_ClassDriver);
USBD_Start(&hUsbDeviceFS);
HAL_NVIC_EnableIRQ(USB_HP_IRQn);
HAL_NVIC_SetPriority(USB_HP_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(USB_HP_IRQn);
HAL_NVIC_SetPriority(USB_LP_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(USB_LP_IRQn);
}
@ -217,3 +220,12 @@ void usb_log(const char *log, uint16_t length) {
// still busy, unable to send log
}
}
void USB_HP_IRQHandler(void)
{
HAL_PCD_IRQHandler(&hpcd_USB_FS);
}
void USB_LP_IRQHandler(void)
{
HAL_PCD_IRQHandler(&hpcd_USB_FS);
}

View file

@ -39,6 +39,9 @@ bool MAX2871::Init(uint32_t f_ref, bool doubler, uint16_t r, bool div2) {
// automatically switch to integer mode if F = 0
regs[5] |= (1UL << 24);
// recommended phase setting
regs[1] |= (1UL << 15);
SetMode(Mode::LowSpur2);
// for all other CP modes the PLL reports unlock condition (output signal appears to be locked)
SetCPMode(CPMode::CP20);

View file

@ -92,9 +92,9 @@ bool HW::Init(WorkRequest wr) {
Si5351.Disable(SiChannel::ReferenceOut);
// Both MAX2871 get a 100MHz reference
Si5351.SetCLK(SiChannel::Source, 100000000, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::Source, HW::PLLRef, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::Source);
Si5351.SetCLK(SiChannel::LO1, 100000000, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::LO1, HW::PLLRef, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
Si5351.Enable(SiChannel::LO1);
// 16MHz FPGA clock
Si5351.SetCLK(SiChannel::FPGA, 16000000, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
@ -133,7 +133,7 @@ bool HW::Init(WorkRequest wr) {
// enable source synthesizer
FPGA::Enable(FPGA::Periphery::SourceChip);
FPGA::SetMode(FPGA::Mode::SourcePLL);
Source.Init(100000000, false, 1, false);
Source.Init(HW::PLLRef, false, 1, false);
Source.SetPowerOutA(MAX2871::Power::n4dbm);
// output B is not used
Source.SetPowerOutB(MAX2871::Power::n4dbm, false);
@ -150,7 +150,7 @@ bool HW::Init(WorkRequest wr) {
FPGA::Disable(FPGA::Periphery::SourceChip);
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::SetMode(FPGA::Mode::LOPLL);
LO1.Init(100000000, false, 1, false);
LO1.Init(HW::PLLRef, false, 1, false);
LO1.SetPowerOutA(MAX2871::Power::n4dbm);
LO1.SetPowerOutB(MAX2871::Power::n4dbm);
if(!LO1.BuildVCOMap()) {

View file

@ -6,8 +6,11 @@
namespace HW {
static constexpr uint32_t ADCSamplerate = 914000;
static constexpr uint32_t IF1 = 60100000;
static constexpr uint32_t IF1 = 60000000;
static constexpr uint32_t IF2 = 250000;
static constexpr uint32_t LO1_minFreq = 25000000;
static constexpr uint32_t MaxSamples = 130944;
static constexpr uint32_t PLLRef = 100000000;
enum class Mode {
Idle,

View file

@ -18,6 +18,7 @@ static uint32_t sampleNum;
static Protocol::PacketInfo p;
static bool active = false;
static uint32_t lastLO2;
static uint32_t actualRBW;
static float port1Measurement, port2Measurement;
@ -40,20 +41,42 @@ static void StartNextSample() {
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, 1120);
break;
case 1:
// Shift first LO to other side
LO1freq = freq - HW::IF1;
LO2freq = HW::IF1 - HW::IF2;
break;
// Shift first LO to other side
// depending on the measurement frequency this is not possible or additive mixing has to be used
if(freq >= HW::IF1 + HW::LO1_minFreq) {
// frequency is high enough to shift 1.LO below measurement frequency
LO1freq = freq - HW::IF1;
break;
} else if(freq <= HW::IF1 - HW::LO1_minFreq) {
// frequency is low enough to add 1.LO to measurement frequency
LO1freq = HW::IF1 - freq;
break;
}
// unable to reach required frequency with 1.LO, skip this signal ID step
signalIDstep++;
/* no break */
case 2:
// Shift both LOs to other side
LO1freq = freq + HW::IF1;
LO2freq = HW::IF1 + HW::IF2;
break;
case 3:
// Shift second LO to other side
LO1freq = freq - HW::IF1;
LO2freq = HW::IF1 + HW::IF2;
break;
// Shift second LO to other side
// depending on the measurement frequency this is not possible or additive mixing has to be used
if(freq >= HW::IF1 + HW::LO1_minFreq) {
// frequency is high enough to shift 1.LO below measurement frequency
LO1freq = freq - HW::IF1;
break;
} else if(freq <= HW::IF1 - HW::LO1_minFreq) {
// frequency is low enough to add 1.LO to measurement frequency
LO1freq = HW::IF1 - freq;
break;
}
// unable to reach required frequency with 1.LO, skip this signal ID step
signalIDstep++;
/* no break */
case 4:
// Use default frequencies with different ADC samplerate to remove images in final IF
LO1freq = freq + HW::IF1;
@ -66,7 +89,7 @@ static void StartNextSample() {
int32_t LO1deviation = (int64_t) LO1.GetActualFrequency() - LO1freq;
LO2freq += LO1deviation;
// 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) > s.RBW / 2) {
if((uint32_t) abs(LO2freq - lastLO2) > actualRBW / 2) {
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
lastLO2 = LO2freq;
@ -88,19 +111,23 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
// in almost all cases a full sweep requires more points than the FPGA can handle at a time
// individually start each point and do the sweep in the uC
FPGA::SetNumberOfPoints(1);
// calculate amount of required points
points = 2 * (s.f_stop - s.f_start) / s.RBW;
// adjust to integer multiple of requested result points (in order to have the same amount of measurements in each bin)
points += s.pointNum - points % s.pointNum;
binSize = points / s.pointNum;
LOG_DEBUG("%u displayed points, resulting in %lu points and bins of size %u", s.pointNum, points, binSize);
// calculate required samples per measurement for requested RBW
// see https://www.tek.com/blog/window-functions-spectrum-analyzers for window factors
constexpr float window_factors[4] = {0.89f, 2.23f, 1.44f, 3.77f};
sampleNum = HW::ADCSamplerate * window_factors[s.WindowType] / s.RBW;
// round up to next multiple of 128
sampleNum += 128 - sampleNum%128;
if(sampleNum >= HW::MaxSamples) {
sampleNum = HW::MaxSamples;
}
actualRBW = HW::ADCSamplerate * window_factors[s.WindowType] / sampleNum;
FPGA::SetSamplesPerPoint(sampleNum);
// calculate amount of required points
points = 2 * (s.f_stop - s.f_start) / actualRBW;
// adjust to integer multiple of requested result points (in order to have the same amount of measurements in each bin)
points += s.pointNum - points % s.pointNum;
binSize = points / s.pointNum;
LOG_DEBUG("%u displayed points, resulting in %lu points and bins of size %u", s.pointNum, points, binSize);
// set initial state
pointCnt = 0;
// enable the required hardware resources

View file

@ -14,10 +14,6 @@
#define LOG_MODULE "VNA"
#include "Log.h"
static constexpr uint32_t IF1 = 60100000;
static constexpr uint32_t IF1_alternate = 57000000;
static constexpr uint32_t IF2 = 250000;
static VNA::SweepCallback sweepCallback;
static Protocol::SweepSettings settings;
static uint16_t pointCnt;
@ -27,11 +23,10 @@ static bool active = false;
using IFTableEntry = struct {
uint16_t pointCnt;
uint32_t IF1;
uint8_t clkconfig[8];
};
static constexpr uint16_t IFTableNumEntries = 100;
static constexpr uint16_t IFTableNumEntries = 500;
static IFTableEntry IFTable[IFTableNumEntries];
static uint16_t IFTableIndexCnt = 0;
@ -58,6 +53,7 @@ bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) {
uint32_t samplesPerPoint = (HW::ADCSamplerate / s.if_bandwidth);
// round up to next multiple of 128 (128 samples are spread across 35 IF2 periods)
samplesPerPoint = ((uint32_t) ((samplesPerPoint + 127) / 128)) * 128;
uint32_t actualBandwidth = HW::ADCSamplerate / samplesPerPoint;
// has to be one less than actual number of samples
FPGA::SetSamplesPerPoint(samplesPerPoint);
@ -70,89 +66,82 @@ bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) {
attenuator = (-1000 - s.cdbm_excitation) / 25;
}
uint32_t last_IF1 = IF1;
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;
bool last_lowband = false;
if(!s.suppressPeaks) {
// invalidate first entry of IFTable, preventing switing of 2.LO in halted callback
IFTable[0].pointCnt = 0xFFFF;
}
// 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 / (points - 1);
// SetFrequency only manipulates the register content in RAM, no SPI communication is done.
// No mode-switch of FPGA necessary here.
// Check which IF frequency is a better fit
uint32_t used_IF = IF1;
// if (freq < 290000000) {
// // for low frequencies the harmonics of the IF and source frequency should not be too close
// uint32_t dist_primary;
// if(freq < IF1) {
// dist_primary = IF1 - freq * (IF1 / freq);
// if (dist_primary > freq / 2) {
// dist_primary = freq - dist_primary;
// }
// } else {
// dist_primary = freq - IF1 * (freq / IF1);
// if (dist_primary > IF1 / 2) {
// dist_primary = IF1 - dist_primary;
// }
// }
// uint32_t dist_alternate;
// if(freq < IF1_alternate) {
// dist_alternate = IF1_alternate - freq * (IF1_alternate / freq);
// if (dist_alternate > freq / 2) {
// dist_alternate = freq - dist_primary;
// }
// } else {
// dist_alternate = freq - IF1_alternate * (freq / IF1_alternate);
// if (dist_alternate > IF1_alternate / 2) {
// dist_alternate = IF1_alternate - dist_primary;
// }
// }
// if(dist_alternate > dist_primary) {
// used_IF = IF1_alternate;
// }
// LOG_INFO("Distance: %lu/%lu", dist_primary, dist_alternate);
// }
bool needs_halt = false;
if (used_IF != last_IF1) {
last_IF1 = used_IF;
LOG_INFO("Changing IF1 to %lu at point %u (f=%lu)", used_IF, i, (uint32_t) freq);
needs_halt = true;
if (IFTableIndexCnt >= IFTableNumEntries) {
LOG_ERR("IF table full, unable to add new entry");
return false;
}
IFTable[IFTableIndexCnt].pointCnt = i;
IFTable[IFTableIndexCnt].IF1 = used_IF;
// Configure LO2 for the changed IF1. This is not necessary right now but it will generate
// the correct clock settings
Si5351.SetCLK(SiChannel::RefLO2, used_IF + IF2, Si5351C::PLL::A, Si5351C::DriveStrength::mA2);
// store calculated clock configuration for later change
Si5351.ReadRawCLKConfig(1, IFTable[IFTableIndexCnt].clkconfig);
IFTableIndexCnt++;
}
uint64_t actualSourceFreq;
bool lowband = false;
if (freq < BandSwitchFrequency) {
needs_halt = true;
lowband = true;
actualSourceFreq = freq;
} else {
Source.SetFrequency(freq);
actualSourceFreq = Source.GetActualFrequency();
}
if (last_lowband && !lowband) {
// additional halt before first highband point to enable highband source
needs_halt = true;
}
LO1.SetFrequency(freq + used_IF);
LO1.SetFrequency(freq + HW::IF1);
uint32_t actualFirstIF = LO1.GetActualFrequency() - 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
LOG_INFO("Changing 2.LO at point %lu to reach correct 2.IF frequency");
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;
Si5351.SetCLK(SiChannel::RefLO2, last_LO2,
Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// store calculated clock configuration for later change
Si5351.ReadRawCLKConfig(1, 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));
}
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(1, IF1 + IF2, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// Si5351.ResetPLL(Si5351C::PLL::B);
// 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);
// Enable mixers/amplifier/PLLs
FPGA::SetWindow(FPGA::Window::None);
FPGA::Enable(FPGA::Periphery::Port1Mixer);
@ -249,15 +238,15 @@ void VNA::SweepHalted() {
}
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++;
// }
if (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);

View file

@ -57,8 +57,6 @@ void DMA1_Channel1_IRQHandler(void);
void DMA1_Channel2_IRQHandler(void);
void DMA1_Channel3_IRQHandler(void);
void DMA1_Channel4_IRQHandler(void);
void USB_HP_IRQHandler(void);
void USB_LP_IRQHandler(void);
void TIM1_TRG_COM_TIM17_IRQHandler(void);
void UCPD1_IRQHandler(void);
/* USER CODE BEGIN EFP */

View file

@ -507,11 +507,6 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef* hpcd)
/* Peripheral clock enable */
__HAL_RCC_USB_CLK_ENABLE();
/* USB interrupt Init */
HAL_NVIC_SetPriority(USB_HP_IRQn, 6, 0);
// HAL_NVIC_EnableIRQ(USB_HP_IRQn);
HAL_NVIC_SetPriority(USB_LP_IRQn, 6, 0);
// HAL_NVIC_EnableIRQ(USB_LP_IRQn);
/* USER CODE BEGIN USB_MspInit 1 */
/* USER CODE END USB_MspInit 1 */
@ -541,9 +536,6 @@ void HAL_PCD_MspDeInit(PCD_HandleTypeDef* hpcd)
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);
/* USB interrupt DeInit */
HAL_NVIC_DisableIRQ(USB_HP_IRQn);
HAL_NVIC_DisableIRQ(USB_LP_IRQn);
/* USER CODE BEGIN USB_MspDeInit 1 */
/* USER CODE END USB_MspDeInit 1 */

View file

@ -60,7 +60,6 @@
extern DMA_HandleTypeDef hdma_spi1_rx;
extern DMA_HandleTypeDef hdma_spi1_tx;
extern TIM_HandleTypeDef htim1;
extern PCD_HandleTypeDef hpcd_USB_FS;
extern TIM_HandleTypeDef htim17;
/* USER CODE BEGIN EV */
@ -219,34 +218,6 @@ void DMA1_Channel4_IRQHandler(void)
/* USER CODE END DMA1_Channel4_IRQn 1 */
}
/**
* @brief This function handles USB high priority interrupt remap.
*/
void USB_HP_IRQHandler(void)
{
/* USER CODE BEGIN USB_HP_IRQn 0 */
/* USER CODE END USB_HP_IRQn 0 */
HAL_PCD_IRQHandler(&hpcd_USB_FS);
/* USER CODE BEGIN USB_HP_IRQn 1 */
/* USER CODE END USB_HP_IRQn 1 */
}
/**
* @brief This function handles USB low priority interrupt remap.
*/
void USB_LP_IRQHandler(void)
{
/* USER CODE BEGIN USB_LP_IRQn 0 */
/* USER CODE END USB_LP_IRQn 0 */
HAL_PCD_IRQHandler(&hpcd_USB_FS);
/* USER CODE BEGIN USB_LP_IRQn 1 */
/* USER CODE END USB_LP_IRQn 1 */
}
/**
* @brief This function handles TIM1 trigger and commutation interrupts and TIM17 global interrupt.
*/

View file

@ -165,8 +165,6 @@ NVIC.TIM1_TRG_COM_TIM17_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:true
NVIC.TimeBase=TIM1_TRG_COM_TIM17_IRQn
NVIC.TimeBaseIP=TIM17
NVIC.UCPD1_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:false
NVIC.USB_HP_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true
NVIC.USB_LP_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true
NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
PA1.GPIOParameters=GPIO_Label
PA1.GPIO_Label=FPGA_AUX1