Basic DFT spectrum analysis working

This commit is contained in:
Jan Käberich 2020-11-07 00:50:59 +01:00
parent f889ec854b
commit ce475fa042
27 changed files with 1313 additions and 563 deletions

View file

@ -12,6 +12,7 @@
static FPGA::HaltedCallback halted_cb;
static uint16_t SysCtrlReg = 0x0000;
static uint16_t ISRMaskReg = 0x0000;
static uint32_t ADC_samplerate;
using namespace FPGAHAL;
@ -27,6 +28,9 @@ void FPGA::WriteRegister(FPGA::Reg reg, uint16_t value) {
Low(CS);
HAL_SPI_Transmit(&FPGA_SPI, (uint8_t*) cmd, 4, 100);
High(CS);
if(reg == Reg::ADCPrescaler) {
ADC_samplerate = Clockrate / value;
}
}
bool FPGA::Configure(Flash *f, uint32_t start_address, uint32_t bitstream_size) {
@ -371,6 +375,23 @@ void FPGA::ResumeHaltedSweep() {
High(CS);
}
void FPGA::SetupDFT(uint32_t f_firstBin, uint32_t f_binSpacing) {
// see FPGA protocol for formulas
uint16_t firstBin = f_firstBin * (1ULL << 16) / ADC_samplerate;
uint16_t binSpacing = f_binSpacing * (1ULL << 24) / ADC_samplerate;
WriteRegister(Reg::DFTFirstBin, firstBin);
WriteRegister(Reg::DFTFreqSpacing, binSpacing);
}
void FPGA::StopDFT() {
DisableInterrupt(Interrupt::DFTReady);
}
void FPGA::StartDFT() {
StopDFT();
EnableInterrupt(Interrupt::DFTReady);
}
FPGA::DFTResult FPGA::ReadDFTResult() {
uint8_t cmd[2] = {0xA0, 0x00};
uint8_t recv[24];

View file

@ -7,6 +7,7 @@ namespace FPGA {
static constexpr uint16_t MaxPoints = 4501;
static constexpr uint16_t DFTbins = 64;
static constexpr uint32_t Clockrate = 102400000UL;
enum class Reg {
InterruptMask = 0x00,
@ -23,8 +24,6 @@ enum class Reg {
MAX2871Def3MSB = 0x0D,
MAX2871Def4LSB = 0x0E,
MAX2871Def4MSB = 0x0F,
DFTSamples = 0x10,
DFTWindowInc = 0x11,
DFTFirstBin = 0x12,
DFTFreqSpacing = 0x13,
};
@ -124,6 +123,9 @@ void WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceRegs, uin
uint8_t attenuation, uint64_t frequency, SettlingTime settling, 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);
void StopDFT();
void StartDFT();
DFTResult ReadDFTResult();
ADCLimits GetADCLimits();
void ResetADCLimits();

View file

@ -315,29 +315,31 @@ void Si5351C::FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1,
// see https://www.silabs.com/documents/public/application-notes/AN619.pdf (page 3/6)
uint32_t a = f_pll / f;
int32_t f_rem = f_pll - f * a;
uint32_t best_b, best_c;
uint32_t best_deviation = UINT32_MAX;
for (uint32_t c = (1UL << 20) - 1; c >= (1UL << 19); c--) {
uint32_t guess_b = (uint64_t) f_rem * c / f;
for (uint32_t b = guess_b; b <= guess_b + 1; b++) {
int32_t f_div = (uint64_t) f * b / c;
uint32_t deviation = abs(f_rem - f_div);
if (deviation < best_deviation) {
best_b = b;
best_c = c;
best_deviation = deviation;
if (deviation == 0) {
break;
}
}
}
if (best_deviation == 0) {
break;
}
}
LOG_DEBUG(
"Optimal divider for %luHz/%luHz is: a=%lu, b=%lu, c=%lu (%luHz deviation)",
f_pll, f, a, best_b, best_c, best_deviation);
// always using the highest modulus divider results in less than 1Hz deviation for all frequencies, that is good enough
uint32_t best_c = (1UL << 20) - 1;
uint32_t best_b = (uint64_t) f_rem * best_c / f;
// uint32_t best_deviation = UINT32_MAX;
// for (uint32_t c = (1UL << 20) - 1; c >= (1UL << 19); c--) {
// uint32_t guess_b = (uint64_t) f_rem * c / f;
// for (uint32_t b = guess_b; b <= guess_b + 1; b++) {
// int32_t f_div = (uint64_t) f * b / c;
// uint32_t deviation = abs(f_rem - f_div);
// if (deviation < best_deviation) {
// best_b = b;
// best_c = c;
// best_deviation = deviation;
// if (deviation <= 3) {
// break;
// }
// }
// }
// if (best_deviation <= 3) {
// break;
// }
// }
// LOG_DEBUG(
// "Optimal divider for %luHz/%luHz is: a=%lu, b=%lu, c=%lu (%luHz deviation)",
// f_pll, f, a, best_b, best_c, best_deviation);
// convert to Si5351C parameters
uint32_t floor = 128 * best_b / best_c;
P1 = 128 * a + floor - 512;

View file

@ -131,10 +131,6 @@ bool HW::Init() {
// Set phase increment according to
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, HW::DFTphaseInc);
// 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

View file

@ -2,6 +2,7 @@
#include <cstdint>
#include "Protocol.hpp"
#include "FPGA/FPGA.hpp"
#define USE_DEBUG_PINS
@ -31,10 +32,9 @@ static constexpr uint32_t LO1_minFreq = 25000000;
static constexpr uint32_t MaxSamples = 130944;
static constexpr uint32_t MinSamples = 16;
static constexpr uint32_t PLLRef = 100000000;
static constexpr uint16_t MaxPoints = 4501;
static constexpr uint8_t ADCprescaler = 102400000UL / ADCSamplerate;
static_assert(ADCprescaler * ADCSamplerate == 102400000UL, "ADCSamplerate can not be reached exactly");
static constexpr uint8_t ADCprescaler = FPGA::Clockrate / ADCSamplerate;
static_assert(ADCprescaler * ADCSamplerate == FPGA::Clockrate, "ADCSamplerate can not be reached exactly");
static constexpr uint16_t DFTphaseInc = 4096 * IF2 / ADCSamplerate;
static_assert(DFTphaseInc * ADCSamplerate == 4096 * IF2, "DFT can not be computed for 2.IF");
@ -43,7 +43,7 @@ static constexpr Protocol::DeviceLimits Limits = {
.maxFreq = 6000000000,
.minIFBW = ADCSamplerate / MaxSamples,
.maxIFBW = ADCSamplerate / MinSamples,
.maxPoints = MaxPoints,
.maxPoints = FPGA::MaxPoints,
.cdbm_min = -4000,
.cdbm_max = 0,
.minRBW = (uint32_t) (ADCSamplerate * 2.23f / MaxSamples),

View file

@ -73,6 +73,9 @@ void Manual::Setup(Protocol::ManualControl m) {
FPGA::Enable(FPGA::Periphery::ExcitePort2, m.PortSwitch == 1);
FPGA::Enable(FPGA::Periphery::PortSwitch);
// Enable new data and sweep halt interrupt
FPGA::EnableInterrupt(FPGA::Interrupt::NewData);
active = true;
FPGA::StartSweep();
}

View file

@ -11,6 +11,8 @@
#define LOG_MODULE "SA"
#include "Log.h"
using namespace HWHAL;
static Protocol::SpectrumAnalyzerSettings s;
static uint32_t pointCnt;
static uint32_t points;
@ -21,10 +23,11 @@ static Protocol::PacketInfo p;
static bool active = false;
static uint32_t lastLO2;
static uint32_t actualRBW;
static bool usingDFT;
static uint16_t DFTpoints;
static bool negativeDFT; // if true, a positive frequency shift at input results in a negative shift at the 2.IF. Handle DFT accordingly
static float port1Measurement, port2Measurement;
using namespace HWHAL;
static float port1Measurement[FPGA::DFTbins], port2Measurement[FPGA::DFTbins];
static void StartNextSample() {
uint64_t freq = s.f_start + (s.f_stop - s.f_start) * pointCnt / (points - 1);
@ -34,16 +37,20 @@ static void StartNextSample() {
case 0:
default:
// reset minimum amplitudes in first signal ID step
port1Measurement = std::numeric_limits<float>::max();
port2Measurement = std::numeric_limits<float>::max();
for (uint16_t i = 0; i < DFTpoints; i++) {
port1Measurement[i] = std::numeric_limits<float>::max();
port2Measurement[i] = std::numeric_limits<float>::max();
}
// Use default LO frequencies
LO1freq = freq + HW::IF1;
LO2freq = HW::IF1 - HW::IF2;
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, 112);
FPGA::WriteRegister(FPGA::Reg::PhaseIncrement, 1120);
negativeDFT = true;
break;
case 1:
LO2freq = HW::IF1 - HW::IF2;
negativeDFT = false;
// 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) {
@ -59,13 +66,15 @@ static void StartNextSample() {
signalIDstep++;
/* no break */
case 2:
// Shift both LOs to other side
// Shift second LOs to other side
LO1freq = freq + HW::IF1;
LO2freq = HW::IF1 + HW::IF2;
negativeDFT = false;
break;
case 3:
// Shift both LO to other side
LO2freq = HW::IF1 + HW::IF2;
// Shift second LO to other side
negativeDFT = true;
// 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
@ -81,6 +90,7 @@ static void StartNextSample() {
/* no break */
case 4:
// Use default frequencies with different ADC samplerate to remove images in final IF
negativeDFT = true;
LO1freq = freq + HW::IF1;
LO2freq = HW::IF1 - HW::IF2;
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, 120);
@ -96,6 +106,17 @@ static void StartNextSample() {
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
lastLO2 = LO2freq;
}
if (usingDFT) {
uint32_t spacing = (s.f_stop - s.f_start) / (points - 1);
uint32_t start = HW::IF2;
if(negativeDFT) {
// needs to look below the start frequency, shift start
start -= spacing * (DFTpoints - 1);
}
FPGA::SetupDFT(start, spacing);
FPGA::StartDFT();
}
// Configure the sampling in the FPGA
FPGA::WriteSweepConfig(0, 0, Source.GetRegisters(), LO1.GetRegisters(), 0,
0, FPGA::SettlingTime::us20, FPGA::Samples::SPPRegister, 0,
@ -111,9 +132,6 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
s = settings;
HW::SetMode(HW::Mode::SA);
FPGA::SetMode(FPGA::Mode::FPGA);
FPGA::DisableInterrupt(FPGA::Interrupt::NewData);
FPGA::DisableInterrupt(FPGA::Interrupt::SweepHalted);
FPGA::EnableInterrupt(FPGA::Interrupt::DFTReady);
// 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);
@ -148,12 +166,17 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
FPGA::Enable(FPGA::Periphery::Port1Mixer);
FPGA::Enable(FPGA::Periphery::Port2Mixer);
// Configure DFT
LOG_INFO("DFT samples: %lu", sampleNum);
FPGA::WriteRegister(FPGA::Reg::DFTSamples, sampleNum - 1);
FPGA::WriteRegister(FPGA::Reg::DFTWindowInc, 65536 / sampleNum);
FPGA::WriteRegister(FPGA::Reg::DFTFirstBin, 17920);
FPGA::WriteRegister(FPGA::Reg::DFTFreqSpacing, 1147);
// automatically select DFT mode for lower RBWs
usingDFT = actualRBW <= 1000;
if (usingDFT) {
DFTpoints = FPGA::DFTbins; // use full DFT in FPGA
FPGA::DisableInterrupt(FPGA::Interrupt::NewData);
} else {
DFTpoints = 1; // can only measure one point at a time
FPGA::StopDFT();
FPGA::EnableInterrupt(FPGA::Interrupt::NewData);
}
lastLO2 = 0;
active = true;
@ -166,26 +189,39 @@ bool SA::MeasurementDone(const FPGA::SamplingResult &result) {
}
FPGA::AbortSweep();
uint16_t i=0;
while(FPGA::GetStatus() & (uint16_t) FPGA::Interrupt::DFTReady) {
auto dft = FPGA::ReadDFTResult();
dft.P1 /= sampleNum;
dft.P2 /= sampleNum;
LOG_INFO("DFT %d: %lu, %lu", i, (uint32_t) dft.P1, (uint32_t) dft.P2);
Log_Flush();
i++;
}
FPGA::DisableInterrupt(FPGA::Interrupt::DFTReady);
FPGA::EnableInterrupt(FPGA::Interrupt::DFTReady);
for(uint16_t i=0;i<DFTpoints;i++) {
float port1, port2;
if (usingDFT) {
// use DFT result
auto dft = FPGA::ReadDFTResult();
port1 = dft.P1;
port2 = dft.P2;
} else {
port1 = abs(std::complex<float>(result.P1I, result.P1Q));
port2 = abs(std::complex<float>(result.P2I, result.P2Q));
}
port1 /= sampleNum;
port2 /= sampleNum;
float port1 = abs(std::complex<float>(result.P1I, result.P1Q))/sampleNum;
float port2 = abs(std::complex<float>(result.P2I, result.P2Q))/sampleNum;
if(port1 < port1Measurement) {
port1Measurement = port1;
uint16_t index = i;
if (negativeDFT) {
// bin order is reversed
index = DFTpoints - i - 1;
}
if(port1 < port1Measurement[index]) {
port1Measurement[index] = port1;
}
if(port2 < port2Measurement[index]) {
port2Measurement[index] = port2;
}
}
if(port2 < port2Measurement) {
port2Measurement = port2;
if (usingDFT) {
FPGA::StopDFT();
// will be started again in StartNextSample
}
// trigger work function
return true;
}
@ -196,74 +232,76 @@ void SA::Work() {
}
if(!s.SignalID || signalIDstep >= 4) {
// this measurement point is done, handle result according to detector
uint16_t binIndex = pointCnt / binSize;
uint32_t pointInBin = pointCnt % binSize;
bool lastPointInBin = pointInBin >= binSize - 1;
auto det = (Detector) s.Detector;
if(det == Detector::Normal) {
det = binIndex & 0x01 ? Detector::PosPeak : Detector::NegPeak;
}
switch(det) {
case Detector::PosPeak:
if(pointInBin == 0) {
p.spectrumResult.port1 = std::numeric_limits<float>::min();
p.spectrumResult.port2 = std::numeric_limits<float>::min();
for(uint16_t i=0;i<DFTpoints;i++) {
uint16_t binIndex = (pointCnt + i) / binSize;
uint32_t pointInBin = (pointCnt + i) % binSize;
bool lastPointInBin = pointInBin >= binSize - 1;
auto det = (Detector) s.Detector;
if(det == Detector::Normal) {
det = binIndex & 0x01 ? Detector::PosPeak : Detector::NegPeak;
}
if(port1Measurement > p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement;
switch(det) {
case Detector::PosPeak:
if(pointInBin == 0) {
p.spectrumResult.port1 = std::numeric_limits<float>::min();
p.spectrumResult.port2 = std::numeric_limits<float>::min();
}
if(port1Measurement[i] > p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement[i];
}
if(port2Measurement[i] > p.spectrumResult.port2) {
p.spectrumResult.port2 = port2Measurement[i];
}
break;
case Detector::NegPeak:
if(pointInBin == 0) {
p.spectrumResult.port1 = std::numeric_limits<float>::max();
p.spectrumResult.port2 = std::numeric_limits<float>::max();
}
if(port1Measurement[i] < p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement[i];
}
if(port2Measurement[i] < p.spectrumResult.port2) {
p.spectrumResult.port2 = port2Measurement[i];
}
break;
case Detector::Sample:
if(pointInBin <= binSize / 2) {
// still in first half of bin, simply overwrite
p.spectrumResult.port1 = port1Measurement[i];
p.spectrumResult.port2 = port2Measurement[i];
}
break;
case Detector::Average:
if(pointInBin == 0) {
p.spectrumResult.port1 = 0;
p.spectrumResult.port2 = 0;
}
p.spectrumResult.port1 += port1Measurement[i];
p.spectrumResult.port2 += port2Measurement[i];
if(lastPointInBin) {
// calculate average
p.spectrumResult.port1 /= binSize;
p.spectrumResult.port2 /= binSize;
}
break;
case Detector::Normal:
// nothing to do, normal detector handled by PosPeak or NegPeak in each sample
break;
}
if(port2Measurement > p.spectrumResult.port2) {
p.spectrumResult.port2 = port2Measurement;
}
break;
case Detector::NegPeak:
if(pointInBin == 0) {
p.spectrumResult.port1 = std::numeric_limits<float>::max();
p.spectrumResult.port2 = std::numeric_limits<float>::max();
}
if(port1Measurement < p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement;
}
if(port2Measurement < p.spectrumResult.port2) {
p.spectrumResult.port2 = port2Measurement;
}
break;
case Detector::Sample:
if(pointInBin <= binSize / 2) {
// still in first half of bin, simply overwrite
p.spectrumResult.port1 = port1Measurement;
p.spectrumResult.port2 = port2Measurement;
}
break;
case Detector::Average:
if(pointInBin == 0) {
p.spectrumResult.port1 = 0;
p.spectrumResult.port2 = 0;
}
p.spectrumResult.port1 += port1Measurement;
p.spectrumResult.port2 += port2Measurement;
if(lastPointInBin) {
// calculate average
p.spectrumResult.port1 /= binSize;
p.spectrumResult.port2 /= binSize;
// Send result to application
p.type = Protocol::PacketType::SpectrumAnalyzerResult;
// measurements are already up to date, fill remaining fields
p.spectrumResult.pointNum = binIndex;
p.spectrumResult.frequency = s.f_start + (s.f_stop - s.f_start) * binIndex / (s.pointNum - 1);
Communication::Send(p);
}
break;
case Detector::Normal:
// nothing to do, normal detector handled by PosPeak or NegPeak in each sample
break;
}
if(lastPointInBin) {
// Send result to application
p.type = Protocol::PacketType::SpectrumAnalyzerResult;
// measurements are already up to date, fill remaining fields
p.spectrumResult.pointNum = binIndex;
p.spectrumResult.frequency = s.f_start + (s.f_stop - s.f_start) * binIndex / (s.pointNum - 1);
Communication::Send(p);
}
// setup for next step
signalIDstep = 0;
if(pointCnt < points - 1) {
pointCnt++;
if(pointCnt < points - DFTpoints) {
pointCnt += DFTpoints;
} else {
pointCnt = 0;
}

View file

@ -187,6 +187,9 @@ bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) {
IFTableIndexCnt = 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;