more flexible USB protocol for VNA settings/measurements

This commit is contained in:
Jan Käberich 2022-08-06 16:22:12 +02:00
parent 74e6a439af
commit 047f6ce981
13 changed files with 203 additions and 82 deletions

View File

@ -463,8 +463,8 @@ void Device::ReceivedData()
handled_len = Protocol::DecodeBuffer(dataBuffer->getBuffer(), dataBuffer->getReceived(), &packet);
dataBuffer->removeBytes(handled_len);
switch(packet.type) {
case Protocol::PacketType::Datapoint:
emit DatapointReceived(packet.datapoint);
case Protocol::PacketType::VNADatapoint:
emit DatapointReceived(packet.VNAdatapoint);
break;
case Protocol::PacketType::ManualStatusV1:
emit ManualStatusReceived(packet.manualStatusV1);

View File

@ -77,7 +77,7 @@ public:
// Returns serial numbers of all connected devices
static std::set<QString> GetDevices();
signals:
void DatapointReceived(Protocol::Datapoint);
void DatapointReceived(Protocol::VNADatapoint<32>*);
void ManualStatusReceived(Protocol::ManualStatusV1);
void SpectrumResultReceived(Protocol::SpectrumAnalyzerResult);
void AmplitudeCorrectionPointReceived(Protocol::AmplitudeCorrectionPoint);

View File

@ -3,6 +3,8 @@
#include "preferences.h"
#include "../VNA_embedded/Application/Communication/Protocol.hpp"
#include <cmath>
static VirtualDevice *connected = nullptr;
using namespace std;
@ -193,20 +195,30 @@ VirtualDevice::VirtualDevice(QString serial)
m.measurements["PORT2"] = res.port2;
emit SAmeasurementReceived(m);
});
connect(dev, &Device::DatapointReceived, [&](Protocol::Datapoint res){
connect(dev, &Device::DatapointReceived, [&](Protocol::VNADatapoint<32> *res){
VNAMeasurement m;
m.pointNum = res.pointNum;
m.pointNum = res->pointNum;
m.Z0 = 50.0;
if(zerospan) {
m.us = res.us;
m.us = res->us;
} else {
m.frequency = res.frequency;
m.dBm = (double) res.cdbm / 100;
m.frequency = res->frequency;
m.dBm = (double) res->cdBm / 100;
}
m.measurements["S11"] = complex<double>(res.real_S11, res.imag_S11);
m.measurements["S21"] = complex<double>(res.real_S21, res.imag_S21);
m.measurements["S12"] = complex<double>(res.real_S12, res.imag_S12);
m.measurements["S22"] = complex<double>(res.real_S22, res.imag_S22);
for(auto map : portStageMapping) {
// map.first is the port (starts at zero)
// map.second is the stage at which this port had the stimulus (starts at zero)
complex<double> ref = res->getValue(map.second, map.first, true);
for(int i=0;i<2;i++) {
complex<double> input = res->getValue(map.second, i, false);
if(!std::isnan(ref.real()) && !std::isnan(input.real())) {
// got both required measurements
QString name = "S"+QString::number(i+1)+QString::number(map.first+1);
m.measurements[name] = input / ref;
}
}
}
delete res;
emit VNAmeasurementReceived(m);
});
} else {
@ -301,6 +313,13 @@ bool VirtualDevice::setVNA(const VirtualDevice::VNASettings &s, std::function<vo
if(s.excitedPorts.size() == 0) {
return setIdle(cb);
}
// create port->stage mapping
portStageMapping.clear();
for(int i=0;i<s.excitedPorts.size();i++) {
portStageMapping[s.excitedPorts[i]] = i;
}
zerospan = (s.freqStart == s.freqStop) && (s.dBmStart == s.dBmStop);
auto pref = Preferences::getInstance();
if(!isCompoundDevice()) {
@ -311,11 +330,13 @@ bool VirtualDevice::setVNA(const VirtualDevice::VNASettings &s, std::function<vo
sd.if_bandwidth = s.IFBW;
sd.cdbm_excitation_start = s.dBmStart * 100;
sd.cdbm_excitation_stop = s.dBmStop * 100;
sd.excitePort1 = find(s.excitedPorts.begin(), s.excitedPorts.end(), 1) != s.excitedPorts.end() ? 1 : 0;
sd.excitePort2 = find(s.excitedPorts.begin(), s.excitedPorts.end(), 2) != s.excitedPorts.end() ? 1 : 0;
sd.stages = s.excitedPorts.size() - 1;
sd.port1Stage = find(s.excitedPorts.begin(), s.excitedPorts.end(), 0) - s.excitedPorts.begin();
sd.port2Stage = find(s.excitedPorts.begin(), s.excitedPorts.end(), 1) - s.excitedPorts.begin();
sd.suppressPeaks = pref.Acquisition.suppressPeaks ? 1 : 0;
sd.fixedPowerSetting = pref.Acquisition.adjustPowerLevel || s.dBmStart != s.dBmStop ? 0 : 1;
sd.logSweep = s.logSweep ? 1 : 0;
sd.syncMode = 0;
return devices[0]->Configure(sd, [=](Device::TransmissionResult r){
if(cb) {
cb(r == Device::TransmissionResult::Ack);
@ -372,6 +393,7 @@ bool VirtualDevice::setSA(const VirtualDevice::SASettings &s, std::function<void
sd.trackingGeneratorPort = s.trackingPort;
sd.trackingGeneratorOffset = s.trackingOffset;
sd.trackingPower = s.trackingPower;
sd.syncMode = 0;
return devices[0]->Configure(sd, [=](Device::TransmissionResult r){
if(cb) {
cb(r == Device::TransmissionResult::Ack);

View File

@ -67,7 +67,7 @@ public:
double IFBW;
int points;
bool logSweep;
std::vector<int> excitedPorts;
std::vector<int> excitedPorts; // port count starts at one
};
class VNAMeasurement {
public:
@ -177,12 +177,15 @@ private:
Status status;
bool isCompound;
std::vector<Device*> devices;
std::vector<int> portMapping;
bool zerospan;
std::map<Device*, Device::TransmissionResult> results;
CompoundDevice *cdev;
std::map<int, std::vector<Protocol::VNADatapoint<32>*>> compoundDataBuffer;
std::map<int, int> portStageMapping; // maps from excitedPort (count starts at zero) to stage (count starts at zero)
};
Q_DECLARE_METATYPE(VirtualDevice::Status)

View File

@ -157,6 +157,7 @@ std::vector<Trace *> TraceModel::getTraces() const
bool TraceModel::PortExcitationRequired(int port)
{
port++;
for(auto t : traces) {
if(t->getSource() == Trace::Source::Live && !t->isPaused()) {
// this trace needs measurements from VNA, check if port has to be excited for its measurement

View File

@ -925,11 +925,11 @@ void VNA::SettingsChanged(bool resetTraces, std::function<void (bool)> cb)
VirtualDevice::VNASettings s = {};
s.IFBW = settings.bandwidth;
if(Preferences::getInstance().Acquisition.alwaysExciteBothPorts) {
for(int i=1;i<=VirtualDevice::getInfo(window->getDevice()).ports;i++) {
for(int i=0;i<VirtualDevice::getInfo(window->getDevice()).ports;i++) {
s.excitedPorts.push_back(i);
}
} else {
for(int i=1;i<=VirtualDevice::getInfo(window->getDevice()).ports;i++) {
for(int i=0;i<VirtualDevice::getInfo(window->getDevice()).ports;i++) {
if(traceModel.PortExcitationRequired(i))
s.excitedPorts.push_back(i);
}

View File

@ -62,7 +62,7 @@ uint16_t Protocol::DecodeBuffer(uint8_t *buf, uint16_t len, PacketInfo *info) {
/* The complete frame has been received, check checksum */
auto type = (PacketType) data[3];
uint32_t crc = *(uint32_t*) &data[length - 4];
if(type != PacketType::Datapoint) {
if(type != PacketType::VNADatapoint) {
uint32_t compare = CRC32(0, data, length - 4);
if(crc != compare) {
// CRC mismatch, remove header
@ -70,6 +70,8 @@ uint16_t Protocol::DecodeBuffer(uint8_t *buf, uint16_t len, PacketInfo *info) {
info->type = PacketType::None;
return data - buf;
}
// Valid packet, copy packet type and payload
memcpy(info, &data[3], length - 7);
} else {
// Datapoint has the CRC set to zero
if(crc != 0x00000000) {
@ -77,17 +79,19 @@ uint16_t Protocol::DecodeBuffer(uint8_t *buf, uint16_t len, PacketInfo *info) {
info->type = PacketType::None;
return data - buf;
}
// Create the datapoint
info->type = (PacketType) data[3];
info->VNAdatapoint = new VNADatapoint<32>;
info->VNAdatapoint->decode(&data[4], length - 8);
}
// Valid packet, copy packet type and payload
memcpy(info, &data[3], length - 7);
return data - buf + length;
}
uint16_t Protocol::EncodePacket(const PacketInfo &packet, uint8_t *dest, uint16_t destsize) {
int16_t payload_size = 0;
switch (packet.type) {
case PacketType::Datapoint: payload_size = sizeof(packet.datapoint); break;
// case PacketType::Datapoint: payload_size = sizeof(packet.datapoint); break;
case PacketType::SweepSettings: payload_size = sizeof(packet.settings); break;
case PacketType::Reference: payload_size = sizeof(packet.reference); break;
case PacketType::DeviceInfo: payload_size = sizeof(packet.info); break;
@ -115,6 +119,7 @@ uint16_t Protocol::EncodePacket(const PacketInfo &packet, uint8_t *dest, uint16_
case PacketType::RequestDeviceStatus:
// no payload
break;
case PacketType::VNADatapoint: payload_size = packet.VNAdatapoint->requiredBufferSize(); break;
case PacketType::None:
break;
}
@ -126,14 +131,18 @@ uint16_t Protocol::EncodePacket(const PacketInfo &packet, uint8_t *dest, uint16_
dest[0] = header;
uint16_t overall_size = payload_size + 8;
memcpy(&dest[1], &overall_size, 2);
memcpy(&dest[3], &packet, payload_size + 1); // one additional byte for the packet type
// Calculate checksum
// Further encoding uses a special case for VNADatapoint packettype
uint32_t crc = 0x00000000;
if(packet.type == PacketType::Datapoint) {
if(packet.type == PacketType::VNADatapoint) {
// CRC calculation takes about 18us which is the bulk of the time required to encode and transmit a datapoint.
// Skip CRC for data points to optimize throughput
dest[3] = (uint8_t) packet.type;
packet.VNAdatapoint->encode(&dest[4], destsize - 8);
crc = 0x00000000;
} else {
// Copy rest of the packet
memcpy(&dest[3], &packet, payload_size + 1); // one additional byte for the packet type
// Calculate the CRC
crc = CRC32(0, dest, overall_size - 4);
}
memcpy(&dest[overall_size - 4], &crc, 4);

View File

@ -1,13 +1,110 @@
#pragma once
#include <cstdint>
#include <cstring>
#include <limits>
#include <complex>
namespace Protocol {
static constexpr uint16_t Version = 11;
static constexpr uint16_t Version = 12;
#pragma pack(push, 1)
enum class Source : uint8_t {
Port1 = 0x01,
Port2 = 0x02,
Port3 = 0x04,
Port4 = 0x08,
Reference = 0x10,
};
template<int s> class VNADatapoint {
public:
VNADatapoint() {
clear();
}
void clear() {
num_values = 0;
pointNum = 0;
cdBm = 0;
frequency = 0;
}
bool addValue(float real, float imag, uint8_t stage, int sourceMask) {
if(num_values >= s) {
return false;
}
real_values[num_values] = real;
imag_values[num_values] = imag;
descr_values[num_values] = stage << 5 | sourceMask;
num_values++;
return true;
}
bool encode(uint8_t *dest, uint16_t destSize) {
if(requiredBufferSize() > destSize) {
return false;
}
memcpy(dest, &frequency, 8);
memcpy(dest+8, &cdBm, 2);
memcpy(dest+10, &pointNum, 2);
dest += 12;
memcpy(dest, real_values, num_values * 4);
dest += num_values * 4;
memcpy(dest, imag_values, num_values * 4);
dest += num_values * 4;
memcpy(dest, descr_values, num_values);
return true;
}
void decode(const uint8_t *buffer, uint16_t size) {
num_values = (size - (8+2+2)) / (1+4+4);
memcpy(&frequency, buffer, 8);
memcpy(&cdBm, buffer+8, 2);
memcpy(&pointNum, buffer+10, 2);
buffer += 12;
memcpy(real_values, buffer, num_values * 4);
buffer += num_values * 4;
memcpy(imag_values, buffer, num_values * 4);
buffer += num_values * 4;
memcpy(descr_values, buffer, num_values);
}
std::complex<double> getValue(uint8_t stage, uint8_t port, bool reference) {
uint8_t sourceMask = 0;
sourceMask |= 0x01 << port;
if(reference) {
sourceMask |= (int) Source::Reference;
}
for(int i=0;i<num_values;i++) {
if(descr_values[i] >> 5 != stage) {
continue;
}
if((descr_values[i] & sourceMask) != sourceMask) {
continue;
}
return std::complex<double>(real_values[i], imag_values[i]);
}
return std::numeric_limits<std::complex<double>>::quiet_NaN();
}
uint16_t requiredBufferSize() {
return 8+2+2+ num_values * (4+4+1);
}
union {
uint64_t frequency;
uint64_t us;
};
int16_t cdBm;
uint16_t pointNum;
private:
float real_values[s];
float imag_values[s];
uint8_t descr_values[s];
uint8_t num_values;
};
using Datapoint = struct _datapoint {
float real_S11, imag_S11;
float real_S21, imag_S21;
@ -33,11 +130,20 @@ using SweepSettings = struct _sweepSettings {
uint16_t points;
uint32_t if_bandwidth;
int16_t cdbm_excitation_start; // in 1/100 dbm
uint8_t excitePort1:1;
uint8_t excitePort2:1;
uint8_t suppressPeaks:1;
uint8_t fixedPowerSetting:1; // if set the attenuator and source PLL power will not be changed across the sweep
uint8_t logSweep:1;
uint16_t unused:2;
uint16_t suppressPeaks:1;
uint16_t fixedPowerSetting:1; // if set the attenuator and source PLL power will not be changed across the sweep
uint16_t logSweep:1;
uint16_t stages:3;
uint16_t port1Stage:3;
uint16_t port2Stage:3;
/*
* 0: no synchronization
* 1: USB synchronization
* 2: External reference synchronization
* 3: Trigger synchronization (not supported yet by hardware)
*/
uint16_t syncMode:2;
int16_t cdbm_excitation_stop; // in 1/100 dbm
};
@ -145,6 +251,13 @@ using SpectrumAnalyzerSettings = struct _spectrumAnalyzerSettings {
uint8_t trackingGenerator :1;
uint8_t applySourceCorrection :1;
uint8_t trackingGeneratorPort :1; // 0 for port1, 1 for port2
/*
* 0: no synchronization
* 1: USB synchronization
* 2: External reference synchronization
* 3: Trigger synchronization (not supported yet by hardware)
*/
uint8_t syncMode :2;
int64_t trackingGeneratorOffset;
int16_t trackingPower;
};
@ -191,7 +304,7 @@ using AcquisitionFrequencySettings = struct _acquisitionfrequencysettigns {
enum class PacketType : uint8_t {
None = 0,
Datapoint = 1,
//Datapoint = 1, // Deprecated, replaced by VNADatapoint
SweepSettings = 2,
ManualStatusV1 = 3,
ManualControlV1 = 4,
@ -217,12 +330,13 @@ enum class PacketType : uint8_t {
AcquisitionFrequencySettings = 24,
DeviceStatusV1 = 25,
RequestDeviceStatus = 26,
VNADatapoint = 27,
};
using PacketInfo = struct _packetinfo {
PacketType type;
union {
Datapoint datapoint;
// Datapoint datapoint; // Deprecated, use VNADatapoint instead
SweepSettings settings;
ReferenceSettings reference;
GeneratorSettings generator;
@ -236,6 +350,11 @@ using PacketInfo = struct _packetinfo {
AmplitudeCorrectionPoint amplitudePoint;
FrequencyCorrection frequencyCorrection;
AcquisitionFrequencySettings acquisitionFrequencySettings;
/*
* When encoding: Pointer may go invalid after call to EncodePacket
* When decoding: VNADatapoint is created on heap by DecodeBuffer, freeing is up to the caller
*/
VNADatapoint<32> *VNAdatapoint;
};
};

View File

@ -130,10 +130,10 @@ void FPGA::SetSamplesPerPoint(uint32_t nsamples) {
WriteRegister(Reg::SamplesPerPoint, nsamples);
}
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool individual_halt) {
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize) {
uint16_t value = 0x0000;
value |= (uint16_t) (stages & 0x07) << 13;
if(individual_halt) {
if(synchronize) {
value |= 0x1000;
}
value |= (port1_stage & 0x07) << 3;

View File

@ -114,7 +114,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 SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool individual_halt = false);
void SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize = false);
void Enable(Periphery p, bool enable = true);
void Disable(Periphery p);
bool IsEnabled(Periphery p);

View File

@ -19,7 +19,7 @@ static uint8_t *USBD_Class_GetDeviceQualifierDescriptor (uint16_t *length);
static usbd_recv_callback_t cb;
static uint8_t usb_receive_buffer[1024];
static uint8_t usb_transmit_fifo[8192];
static uint8_t usb_transmit_fifo[6144];
static uint16_t usb_transmit_read_index = 0;
static uint16_t usb_transmit_fifo_level = 0;
static bool data_transmission_active = false;

View File

@ -224,7 +224,7 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
FPGA::SetWindow((FPGA::Window) s.WindowType);
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::Enable(FPGA::Periphery::LO1RF);
FPGA::SetupSweep(0, s.trackingGeneratorPort == 1, s.trackingGeneratorPort == 0);
FPGA::SetupSweep(0, s.trackingGeneratorPort == 1, s.trackingGeneratorPort == 0, s.syncMode != 0);
FPGA::Enable(FPGA::Periphery::PortSwitch, s.trackingGenerator);
FPGA::Enable(FPGA::Periphery::Amplifier, s.trackingGenerator);
FPGA::Enable(FPGA::Periphery::Port1Mixer);

View File

@ -22,9 +22,8 @@
static Protocol::SweepSettings settings;
static uint16_t pointCnt;
static uint8_t stageCnt;
static uint8_t stages;
static double logMultiplier, logFrequency;
static Protocol::Datapoint data;
static Protocol::VNADatapoint<32> data;
static bool active = false;
static Si5351C::DriveStrength fixedPowerLowband;
static bool adcShifted;
@ -81,12 +80,6 @@ bool VNA::Setup(Protocol::SweepSettings s) {
VNA::Stop();
vTaskDelay(5);
HW::SetMode(HW::Mode::VNA);
if(s.excitePort1 == 0 && s.excitePort2 == 0) {
// both ports disabled, nothing to do
HW::SetIdle();
active = false;
return false;
}
// Abort possible active sweep first
FPGA::SetMode(FPGA::Mode::FPGA);
FPGA::WriteRegister(FPGA::Reg::ADCPrescaler, HW::getADCPrescaler());
@ -285,19 +278,7 @@ bool VNA::Setup(Protocol::SweepSettings s) {
FPGA::Enable(FPGA::Periphery::SourceRF);
FPGA::Enable(FPGA::Periphery::LO1Chip);
FPGA::Enable(FPGA::Periphery::LO1RF);
if(s.excitePort1 && s.excitePort2) {
// two stages, port 1 first, followed by port 2
FPGA::SetupSweep(1, 0, 1);
stages = 2;
} else if(s.excitePort1) {
// one stage, port 1 only
FPGA::SetupSweep(0, 0, 1);
stages = 1;
} else {
// one stage, port 2 only
FPGA::SetupSweep(0, 1, 0);
stages = 1;
}
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.syncMode != 0);
FPGA::Enable(FPGA::Periphery::PortSwitch);
pointCnt = 0;
stageCnt = 0;
@ -315,9 +296,10 @@ bool VNA::Setup(Protocol::SweepSettings s) {
static void PassOnData() {
Protocol::PacketInfo info;
info.type = Protocol::PacketType::Datapoint;
info.datapoint = data;
info.type = Protocol::PacketType::VNADatapoint;
info.VNAdatapoint = &data;
Communication::Send(info);
data.clear();
}
bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
@ -330,11 +312,9 @@ bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
return false;
}
// normal sweep mode
auto port1_raw = std::complex<float>(result.P1I, result.P1Q);
auto port2_raw = std::complex<float>(result.P2I, result.P2Q);
auto ref = std::complex<float>(result.RefI, result.RefQ);
auto port1 = port1_raw / ref;
auto port2 = port2_raw / ref;
data.addValue(result.P1I, result.P1Q, stageCnt, (int) Protocol::Source::Port1);
data.addValue(result.P2I, result.P2Q, stageCnt, (int) Protocol::Source::Port2);
data.addValue(result.RefI, result.RefQ, stageCnt, (int) Protocol::Source::Port1 | (int) Protocol::Source::Port2 | (int) Protocol::Source::Reference);
data.pointNum = pointCnt;
if(zerospan) {
uint64_t timestamp = HW::getLastISRTimestamp();
@ -348,24 +328,11 @@ bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
} else {
// non-zero span, set frequency/power
data.frequency = getPointFrequency(pointCnt);
data.cdbm = settings.cdbm_excitation_start + (settings.cdbm_excitation_stop - settings.cdbm_excitation_start) * pointCnt / (settings.points - 1);
}
if(stageCnt == 0 && settings.excitePort1) {
// stimulus is present at port 1
data.real_S11 = port1.real();
data.imag_S11 = port1.imag();
data.real_S21 = port2.real();
data.imag_S21 = port2.imag();
} else {
// stimulus is present at port 2
data.real_S12 = port1.real();
data.imag_S12 = port1.imag();
data.real_S22 = port2.real();
data.imag_S22 = port2.imag();
data.cdBm = settings.cdbm_excitation_start + (settings.cdbm_excitation_stop - settings.cdbm_excitation_start) * pointCnt / (settings.points - 1);
}
// figure out whether this sweep point is complete
stageCnt++;
if(stageCnt == stages) {
if(stageCnt > settings.stages) {
// point is complete
stageCnt = 0;
STM::DispatchToInterrupt(PassOnData);
@ -507,7 +474,7 @@ void VNA::PrintStatus() {
HAL_Delay(10);
LOG_INFO("Points: %d/%d", pointCnt, settings.points);
HAL_Delay(10);
LOG_INFO("Stages: %d/%d", stageCnt, stages);
LOG_INFO("Stages: %d/%d", stageCnt, settings.stages);
HAL_Delay(10);
LOG_INFO("FPGA status: 0x%04x", FPGA::GetStatus());
}