proof-of-concept spectrum analyzer mode

This commit is contained in:
Jan Käberich 2020-09-17 15:51:20 +02:00
parent 76875c2316
commit 38e73365df
33 changed files with 942 additions and 82 deletions

View file

@ -16,6 +16,7 @@
#include "Hardware.hpp"
#include "Manual.hpp"
#include "Generator.hpp"
#include "SpectrumAnalyzer.hpp"
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE "App"
@ -37,6 +38,7 @@ static Flash flash = Flash(&hspi1, FLASH_CS_GPIO_Port, FLASH_CS_Pin);
#define FLAG_USB_PACKET 0x01
#define FLAG_DATAPOINT 0x02
#define FLAG_WORK_REQUIRED 0x04
static void VNACallback(Protocol::Datapoint res) {
result = res;
@ -50,6 +52,11 @@ static void USBPacketReceived(Protocol::PacketInfo p) {
xTaskNotifyFromISR(handle, FLAG_USB_PACKET, eSetBits, &woken);
portYIELD_FROM_ISR(woken);
}
static void HardwareWorkRequired() {
BaseType_t woken = false;
xTaskNotifyFromISR(handle, FLAG_WORK_REQUIRED, eSetBits, &woken);
portYIELD_FROM_ISR(woken);
}
void App_Start() {
handle = xTaskGetCurrentTaskHandle();
@ -90,7 +97,7 @@ void App_Start() {
EN_6V_GPIO_Port->BSRR = EN_6V_Pin;
#endif
if (!HW::Init()) {
if (!HW::Init(HardwareWorkRequired)) {
LOG_CRIT("Initialization failed, unable to start");
LED::Error(4);
}
@ -108,6 +115,9 @@ void App_Start() {
uint32_t notification;
if(xTaskNotifyWait(0x00, UINT32_MAX, &notification, 100) == pdPASS) {
// something happened
if(notification & FLAG_WORK_REQUIRED) {
HW::Work();
}
if(notification & FLAG_DATAPOINT) {
Protocol::PacketInfo packet;
packet.type = Protocol::PacketType::Datapoint;
@ -143,6 +153,12 @@ void App_Start() {
Generator::Setup(packet.generator);
Communication::SendWithoutPayload(Protocol::PacketType::Ack);
break;
case Protocol::PacketType::SpectrumAnalyzerSettings:
sweepActive = false;
LOG_INFO("Updating spectrum analyzer settings");
SA::Setup(packet.spectrumSettings);
Communication::SendWithoutPayload(Protocol::PacketType::Ack);
break;
#ifdef HAS_FLASH
case Protocol::PacketType::ClearFlash:
HW::SetMode(HW::Mode::Idle);
@ -191,7 +207,7 @@ void App_Start() {
LOG_WARN("Timed out waiting for point, last received point was %d (Status 0x%04x)", result.pointNum, FPGA::GetStatus());
FPGA::AbortSweep();
// restart the current sweep
HW::Init();
HW::Init(HardwareWorkRequired);
HW::Ref::update();
VNA::Setup(settings, VNACallback);
sweepActive = true;

View file

@ -358,6 +358,50 @@ static int16_t EncodeManualControl(Protocol::ManualControl d, uint8_t *buf,
return e.getSize();
}
static Protocol::SpectrumAnalyzerSettings DecodeSpectrumAnalyzerSettings(uint8_t *buf) {
Protocol::SpectrumAnalyzerSettings d;
Decoder e(buf);
e.get<uint64_t>(d.f_start);
e.get<uint64_t>(d.f_stop);
e.get<uint32_t>(d.RBW);
e.get<uint16_t>(d.pointNum);
d.WindowType = e.getBits(2);
d.SignalID = e.getBits(1);
d.Detector = e.getBits(3);
return d;
}
static int16_t EncodeSpectrumAnalyzerSettings(Protocol::SpectrumAnalyzerSettings d, uint8_t *buf,
uint16_t bufSize) {
Encoder e(buf, bufSize);
e.add<uint64_t>(d.f_start);
e.add<uint64_t>(d.f_stop);
e.add<uint32_t>(d.RBW);
e.add<uint16_t>(d.pointNum);
e.addBits(d.WindowType, 2);
e.addBits(d.SignalID, 1);
e.addBits(d.Detector, 3);
return e.getSize();
}
static Protocol::SpectrumAnalyzerResult DecodeSpectrumAnalyzerResult(uint8_t *buf) {
Protocol::SpectrumAnalyzerResult d;
Decoder e(buf);
e.get<float>(d.port1);
e.get<float>(d.port2);
e.get<uint64_t>(d.frequency);
e.get<uint16_t>(d.pointNum);
return d;
}
static int16_t EncodeSpectrumAnalyzerResult(Protocol::SpectrumAnalyzerResult d, uint8_t *buf,
uint16_t bufSize) {
Encoder e(buf, bufSize);
e.add<float>(d.port1);
e.add<float>(d.port2);
e.add<uint64_t>(d.frequency);
e.add<uint16_t>(d.pointNum);
return e.getSize();
}
static Protocol::FirmwarePacket DecodeFirmwarePacket(uint8_t *buf) {
Protocol::FirmwarePacket d;
// simple packet format, memcpy is faster than using the decoder
@ -446,6 +490,12 @@ uint16_t Protocol::DecodeBuffer(uint8_t *buf, uint16_t len, PacketInfo *info) {
case PacketType::Generator:
info->generator = DecodeGeneratorSettings(&data[4]);
break;
case PacketType::SpectrumAnalyzerSettings:
info->spectrumSettings = DecodeSpectrumAnalyzerSettings(&data[4]);
break;
case PacketType::SpectrumAnalyzerResult:
info->spectrumResult = DecodeSpectrumAnalyzerResult(&data[4]);
break;
case PacketType::Ack:
case PacketType::PerformFirmwareUpdate:
case PacketType::ClearFlash:
@ -486,6 +536,12 @@ uint16_t Protocol::EncodePacket(PacketInfo packet, uint8_t *dest, uint16_t dests
case PacketType::Generator:
payload_size = EncodeGeneratorSettings(packet.generator, &dest[4], destsize - 8);
break;
case PacketType::SpectrumAnalyzerSettings:
payload_size = EncodeSpectrumAnalyzerSettings(packet.spectrumSettings, &dest[4], destsize - 8);
break;
case PacketType::SpectrumAnalyzerResult:
payload_size = EncodeSpectrumAnalyzerResult(packet.spectrumResult, &dest[4], destsize - 8);
break;
case PacketType::Ack:
case PacketType::PerformFirmwareUpdate:
case PacketType::ClearFlash:

View file

@ -102,10 +102,18 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings {
uint64_t f_start;
uint64_t f_stop;
uint32_t RBW;
uint16_t pointNum;
uint8_t WindowType :2;
uint8_t SignalID :1;
uint8_t Detector :3;
};
using SpectrumAnalyzerResult = struct _spectrumAnalyzerResult {
float port1;
float port2;
uint64_t frequency;
uint16_t pointNum;
};
static constexpr uint16_t FirmwareChunkSize = 256;
using FirmwarePacket = struct _firmwarePacket {
@ -127,6 +135,8 @@ enum class PacketType : uint8_t {
Nack = 10,
Reference = 11,
Generator = 12,
SpectrumAnalyzerSettings = 13,
SpectrumAnalyzerResult = 14,
};
using PacketInfo = struct _packetinfo {
@ -138,8 +148,10 @@ using PacketInfo = struct _packetinfo {
GeneratorSettings generator;
DeviceInfo info;
ManualControl manual;
ManualStatus status;
FirmwarePacket firmware;
ManualStatus status;
SpectrumAnalyzerSettings spectrumSettings;
SpectrumAnalyzerResult spectrumResult;
};
};

View file

@ -4,7 +4,7 @@
#include "delay.hpp"
#include <cmath>
#define LOG_LEVEL LOG_LEVEL_WARN
#define LOG_LEVEL LOG_LEVEL_ERR
#define LOG_MODULE "MAX2871"
#include "Log.h"
@ -198,7 +198,7 @@ bool MAX2871::SetFrequency(uint64_t f) {
approx.num, approx.denom, abs(rem_f - rem_approx));
}
uint64_t f_set = (uint64_t) N * f_PFD + (f_PFD * approx.num) / approx.denom;
uint64_t f_set = (uint64_t) N * f_PFD + rem_approx;
f_set /= (1UL << div);
// write values to registers

View file

@ -57,6 +57,9 @@ public:
uint32_t* GetRegisters() {
return regs;
}
uint64_t GetActualFrequency() {
return outputFrequency;
}
private:
static constexpr uint64_t MaxFreq = 6100000000; // 6GHz according to datasheet, but slight overclocking is possible

View file

@ -6,6 +6,7 @@
#include "Exti.hpp"
#include "VNA.hpp"
#include "Manual.hpp"
#include "SpectrumAnalyzer.hpp"
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE "HW"
@ -15,8 +16,6 @@ static uint32_t extOutFreq = 0;
static bool extRefInUse = false;
HW::Mode activeMode;
static constexpr uint32_t IF1 = 60100000;
static constexpr uint32_t IF2 = 250000;
static Protocol::ReferenceSettings ref;
using namespace HWHAL;
@ -30,6 +29,9 @@ static void HaltedCallback() {
break;
}
}
static HW::WorkRequest requestWork;
static void ReadComplete(FPGA::SamplingResult result) {
bool needs_work = false;
switch(activeMode) {
@ -39,11 +41,14 @@ static void ReadComplete(FPGA::SamplingResult result) {
case HW::Mode::Manual:
needs_work = Manual::MeasurementDone(result);
break;
case HW::Mode::SA:
needs_work = SA::MeasurementDone(result);
break;
default:
break;
}
if(needs_work) {
HAL_NVIC_SetPendingIRQ(COMP4_IRQn);
if(needs_work && requestWork) {
requestWork();
}
}
@ -51,9 +56,7 @@ static void FPGA_Interrupt(void*) {
FPGA::InitiateSampleRead(ReadComplete);
}
/* low priority interrupt to handle additional workload after FPGA interrupt */
extern "C" {
void COMP4_IRQHandler(void) {
void HW::Work() {
switch(activeMode) {
case HW::Mode::VNA:
VNA::Work();
@ -61,16 +64,17 @@ void COMP4_IRQHandler(void) {
case HW::Mode::Manual:
Manual::Work();
break;
case HW::Mode::SA:
SA::Work();
break;
default:
break;
}
}
}
bool HW::Init() {
bool HW::Init(WorkRequest wr) {
requestWork = wr;
LOG_DEBUG("Initializing...");
HAL_NVIC_SetPriority(COMP4_IRQn, 15, 0);
HAL_NVIC_EnableIRQ(COMP4_IRQn);
activeMode = Mode::Idle;
@ -179,10 +183,9 @@ void HW::SetMode(Mode mode) {
default:
break;
}
if(activeMode == Mode::Manual && mode != Mode::Idle) {
// do a full initialization when switching from manual mode to anything else than idle
// (making sure that any changes made in manual mode are reverted)
HW::Init();
if(mode != Mode::Idle && activeMode != Mode::Idle) {
// do a full initialization when switching directly between modes
HW::Init(requestWork);
}
SetIdle();
activeMode = mode;

View file

@ -5,6 +5,10 @@
namespace HW {
static constexpr uint32_t ADCSamplerate = 914000;
static constexpr uint32_t IF1 = 60100000;
static constexpr uint32_t IF2 = 250000;
enum class Mode {
Idle,
Manual,
@ -12,9 +16,12 @@ enum class Mode {
SA,
};
bool Init();
using WorkRequest = void (*)(void);
bool Init(WorkRequest wr);
void SetMode(Mode mode);
void SetIdle();
void Work();
bool GetTemps(uint8_t *source, uint8_t *lo);
void fillDeviceInfo(Protocol::DeviceInfo *info);

View file

@ -97,6 +97,7 @@ void Manual::Work() {
}
Protocol::PacketInfo p;
p.type = Protocol::PacketType::Status;
p.status = status;
uint16_t isr_flags = FPGA::GetStatus();
if (!(isr_flags & 0x0002)) {
p.status.source_locked = 1;

View file

@ -0,0 +1,207 @@
#include "SpectrumAnalyzer.hpp"
#include "Hardware.hpp"
#include "HW_HAL.hpp"
#include <complex.h>
#include <limits>
#include "Communication.h"
#define LOG_LEVEL LOG_LEVEL_DEBUG
#define LOG_MODULE "SA"
#include "Log.h"
static Protocol::SpectrumAnalyzerSettings s;
static uint32_t pointCnt;
static uint32_t points;
static uint32_t binSize;
static uint8_t signalIDstep;
static uint32_t sampleNum;
static Protocol::PacketInfo p;
static bool active = false;
static float port1Measurement, port2Measurement;
using namespace HWHAL;
static void StartNextSample() {
uint64_t freq = s.f_start + (s.f_stop - s.f_start) * pointCnt / (points - 1);
uint64_t LO1freq;
uint32_t LO2freq;
switch(signalIDstep) {
case 0:
default:
// reset minimum amplitudes in first signal ID step
port1Measurement = std::numeric_limits<float>::max();
port2Measurement = std::numeric_limits<float>::max();
LO1freq = freq + HW::IF1;
LO2freq = HW::IF1 - HW::IF2;
break;
case 1:
LO1freq = freq - HW::IF1;
LO2freq = HW::IF1 - HW::IF2;
break;
case 2:
LO1freq = freq + HW::IF1;
LO2freq = HW::IF1 + HW::IF2;
break;
case 3:
LO1freq = freq - HW::IF1;
LO2freq = HW::IF1 + HW::IF2;
break;
}
LO1.SetFrequency(LO1freq);
// LO1 is not able to reach all frequencies with the required precision, adjust LO2 to account for deviation
int32_t LO1deviation = (int64_t) LO1.GetActualFrequency() - LO1freq;
LO2freq += LO1deviation;
// Adjust LO2 PLL
// Generate second LO with Si5351
Si5351.SetCLK(SiChannel::Port1LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
Si5351.SetCLK(SiChannel::Port2LO2, LO2freq, Si5351C::PLL::B, Si5351C::DriveStrength::mA2);
// Configure the sampling in the FPGA
FPGA::WriteSweepConfig(0, 0, Source.GetRegisters(), LO1.GetRegisters(), 0,
0, FPGA::SettlingTime::us20, FPGA::Samples::SPPRegister, 0,
FPGA::LowpassFilter::M947);
FPGA::StartSweep();
}
void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
LOG_DEBUG("Setting up...");
s = settings;
HW::SetMode(HW::Mode::SA);
FPGA::AbortSweep();
FPGA::SetMode(FPGA::Mode::FPGA);
// 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 = (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;
FPGA::SetSamplesPerPoint(sampleNum);
// set initial state
pointCnt = 0;
// enable the required hardware resources
Si5351.Enable(SiChannel::Port1LO2);
Si5351.Enable(SiChannel::Port2LO2);
FPGA::SetWindow((FPGA::Window) s.WindowType);
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::Enable(FPGA::Periphery::LO1RF);
FPGA::Enable(FPGA::Periphery::ExcitePort1);
FPGA::Enable(FPGA::Periphery::Port1Mixer);
FPGA::Enable(FPGA::Periphery::Port2Mixer);
active = true;
StartNextSample();
}
bool SA::MeasurementDone(FPGA::SamplingResult result) {
if(!active) {
return false;
}
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;
}
if(port2 < port2Measurement) {
port2Measurement = port2;
}
// trigger work function
return true;
}
void SA::Work() {
if(!active) {
return;
}
if(!s.SignalID || signalIDstep >= 3) {
// 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();
}
if(port1Measurement > p.spectrumResult.port1) {
p.spectrumResult.port1 = port1Measurement;
}
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;
}
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++;
} else {
pointCnt = 0;
}
} else {
// more measurements required for signal ID
signalIDstep++;
}
StartNextSample();
}
void SA::Stop() {
active = false;
FPGA::AbortSweep();
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "Protocol.hpp"
#include "FPGA/FPGA.hpp"
namespace SA {
enum class Detector {
PosPeak = 0x00,
NegPeak = 0x01,
Sample = 0x02,
Normal = 0x03,
Average = 0x04,
};
void Setup(Protocol::SpectrumAnalyzerSettings settings);
bool MeasurementDone(FPGA::SamplingResult result);
void Work();
void Stop();
}

View file

@ -55,7 +55,7 @@ bool VNA::Setup(Protocol::SweepSettings s, SweepCallback cb) {
uint16_t points = settings.points <= FPGA::MaxPoints ? settings.points : FPGA::MaxPoints;
// Configure sweep
FPGA::SetNumberOfPoints(points);
uint32_t samplesPerPoint = (1000000 / s.if_bandwidth);
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;
// has to be one less than actual number of samples