partial driver for SNA5000A
This commit is contained in:
parent
4c0d18b5f4
commit
772ef44d49
@ -0,0 +1,546 @@
|
||||
#include "sna5000adriver.h"
|
||||
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
#include "Util/util.h"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QDateTime>
|
||||
#include <QApplication>
|
||||
|
||||
SNA5000ADriver::SNA5000ADriver()
|
||||
: DeviceTCPDriver("SNA5000A")
|
||||
{
|
||||
diffGen = new TraceDifferenceGenerator<VNAPoint>([=](const VNAPoint &p){
|
||||
VNAMeasurement m;
|
||||
m.Z0 = 50.0;
|
||||
m.pointNum = p.index;
|
||||
m.frequency = p.frequency;
|
||||
m.dBm = excitationPower;
|
||||
m.measurements = p.data;
|
||||
emit VNAmeasurementReceived(m);
|
||||
});
|
||||
|
||||
traceReader.waitingForResponse = false;
|
||||
// connect(&traceTimer, &QTimer::timeout, this, &SNA5000ADriver::extractTracePoints);
|
||||
// traceTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
SNA5000ADriver::~SNA5000ADriver()
|
||||
{
|
||||
delete diffGen;
|
||||
}
|
||||
|
||||
std::set<QString> SNA5000ADriver::GetAvailableDevices()
|
||||
{
|
||||
std::set<QString> ret;
|
||||
|
||||
// attempt to establish a connection to check if the device is available and extract the serial number
|
||||
detectedDevices.clear();
|
||||
auto sock = QTcpSocket();
|
||||
for(auto address : getSearchAddresses()) {
|
||||
sock.connectToHost(address, 5024);
|
||||
if(sock.waitForConnected(50)) {
|
||||
// connection successful
|
||||
sock.waitForReadyRead(100);
|
||||
auto line = QString(sock.readLine());
|
||||
if(line.startsWith("Welcome to the SCPI instrument 'Siglent SNA5")) {
|
||||
// throw away command prompt ">>"
|
||||
sock.readLine();
|
||||
// looks like we are connected to the correct instrument, request serial number
|
||||
sock.write("*IDN?\r\n");
|
||||
sock.waitForReadyRead(100);
|
||||
sock.read(1);
|
||||
sock.waitForReadyRead(100);
|
||||
line = QString(sock.readLine());
|
||||
auto fields = line.split(",");
|
||||
if(fields.size() == 4) {
|
||||
detectedDevices[fields[2]] = address;
|
||||
ret.insert(fields[2]);
|
||||
}
|
||||
}
|
||||
sock.disconnect();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::connectTo(QString serial)
|
||||
{
|
||||
if(connected) {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// check if this device is actually available
|
||||
QHostAddress address;
|
||||
bool available = false;
|
||||
for(auto d : detectedDevices) {
|
||||
if(d.first == serial) {
|
||||
address = d.second;
|
||||
available = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!available) {
|
||||
// no location information about this device available
|
||||
return false;
|
||||
}
|
||||
|
||||
dataSocket.connectToHost(address, 5025);
|
||||
|
||||
// check if connection succeeds
|
||||
if(!dataSocket.waitForConnected(1000)) {
|
||||
// socket failed
|
||||
dataSocket.close();
|
||||
InformationBox::ShowError("Error", "TCP connection timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(&dataSocket, qOverload<QAbstractSocket::SocketError>(&QTcpSocket::errorOccurred), this, [this](QAbstractSocket::SocketError err) {
|
||||
if(err == QAbstractSocket::SocketTimeoutError) {
|
||||
// ignore, these are triggered by the query function
|
||||
} else {
|
||||
emit ConnectionLost();
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
// grab model information
|
||||
dataSocket.write("*IDN?\r\n");
|
||||
dataSocket.waitForReadyRead(100);
|
||||
auto line = QString(dataSocket.readLine());
|
||||
auto fields = line.split(",");
|
||||
if(fields.size() != 4) {
|
||||
dataSocket.close();
|
||||
InformationBox::ShowError("Error", "Invalid *IDN? response");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->serial = fields[2];
|
||||
|
||||
info = Info();
|
||||
info.hardware_version = fields[1];
|
||||
info.firmware_version = fields[3].trimmed();
|
||||
|
||||
const QStringList supportedDevices = {"SNA5002A", "SNA5004A", "SNA5012A", "SNA5014A"};
|
||||
|
||||
if(!supportedDevices.contains(info.hardware_version)) {
|
||||
dataSocket.close();
|
||||
InformationBox::ShowError("Error", "Invalid hardware version: " + info.hardware_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
info.supportedFeatures.insert(DeviceDriver::Feature::VNA);
|
||||
info.supportedFeatures.insert(DeviceDriver::Feature::VNAFrequencySweep);
|
||||
info.supportedFeatures.insert(DeviceDriver::Feature::Generator);
|
||||
|
||||
// Extract limits
|
||||
info.Limits.VNA.ports = queryInt(":SERVICE:PORT:COUNT?");
|
||||
info.Limits.VNA.minFreq = queryInt(":SERVICE:SWEEP:FREQENCY:MINIMUM?");
|
||||
info.Limits.VNA.maxFreq = queryInt(":SERVICE:SWEEP:FREQENCY:MAXIMUM?");
|
||||
info.Limits.VNA.maxPoints = queryInt(":SERVICE:SWEEP:POINTS?");
|
||||
info.Limits.VNA.minIFBW = 10;
|
||||
info.Limits.VNA.maxIFBW = 3000000;
|
||||
info.Limits.VNA.mindBm = -55;
|
||||
info.Limits.VNA.maxdBm = 10;
|
||||
|
||||
info.Limits.Generator.ports = info.Limits.VNA.ports;
|
||||
info.Limits.Generator.minFreq = info.Limits.VNA.minFreq;
|
||||
info.Limits.Generator.maxFreq = info.Limits.VNA.maxFreq;
|
||||
info.Limits.Generator.mindBm = info.Limits.VNA.mindBm;
|
||||
info.Limits.Generator.maxdBm = info.Limits.VNA.maxdBm;
|
||||
|
||||
connected = true;
|
||||
|
||||
// reset to default configuration
|
||||
dataSocket.write("*RST\r\n");
|
||||
|
||||
emit InfoUpdated();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SNA5000ADriver::disconnect()
|
||||
{
|
||||
traceReaderStop();
|
||||
connected = false;
|
||||
dataSocket.close();
|
||||
}
|
||||
|
||||
DeviceDriver::Info SNA5000ADriver::getInfo()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
std::set<DeviceDriver::Flag> SNA5000ADriver::getFlags()
|
||||
{
|
||||
return std::set<DeviceDriver::Flag>();
|
||||
}
|
||||
|
||||
QString SNA5000ADriver::getStatus()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QStringList SNA5000ADriver::availableVNAMeasurements()
|
||||
{
|
||||
switch(info.Limits.VNA.ports) {
|
||||
case 2:
|
||||
return {"S11", "S12", "S21", "S22"};
|
||||
case 4:
|
||||
return {"S11", "S12", "S13", "S14", "S21", "S22", "S23", "S24", "S31", "S32", "S33", "S34", "S41", "S42", "S43", "S44"};
|
||||
default:
|
||||
return {""};
|
||||
}
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::setVNA(const VNASettings &s, std::function<void (bool)> cb)
|
||||
{
|
||||
excitationPower = s.dBmStart;
|
||||
excitedPorts = s.excitedPorts;
|
||||
|
||||
if(!traceReaderStop()) {
|
||||
emit ConnectionLost();
|
||||
return false;
|
||||
}
|
||||
|
||||
// disable unused traces
|
||||
for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) {
|
||||
write(":DISP:WIND:TRAC"+QString::number(i)+" 0");
|
||||
}
|
||||
// enable a trace for every active port
|
||||
for(auto p : s.excitedPorts) {
|
||||
write(":DISP:WIND:TRAC"+QString::number(p)+" 1");
|
||||
// set the parameter to force the stimulus active at the port
|
||||
write(":CALC:PAR"+QString::number(p)+":DEF S"+QString::number(p)+QString::number(p));
|
||||
}
|
||||
|
||||
// configure the sweep
|
||||
write(":SENS:FREQ:STAR "+QString::number(s.freqStart));
|
||||
write(":SENS:FREQ:STOP "+QString::number(s.freqStop));
|
||||
write(":SENS:BWID "+QString::number(s.IFBW));
|
||||
write(":SOUR:POW "+QString::number(s.dBmStart));
|
||||
write(":SENS:SWEEP:POINTS "+QString::number(s.points));
|
||||
|
||||
// traceTimer.start(100);
|
||||
if(cb) {
|
||||
cb(true);
|
||||
}
|
||||
traceReaderRestart();
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList SNA5000ADriver::availableSGPorts()
|
||||
{
|
||||
switch(info.Limits.Generator.ports) {
|
||||
case 2:
|
||||
return {"PORT1", "PORT2"};
|
||||
case 4:
|
||||
return {"PORT1", "PORT2", "PORT3", "PORT4"};
|
||||
default:
|
||||
return {""};
|
||||
}
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::setSG(const SGSettings &s)
|
||||
{
|
||||
// enable SA mode (generator control is only available there)
|
||||
write(":CALC:CUST:DEF \"SA\"");
|
||||
|
||||
if(s.port == 0) {
|
||||
// turn off all ports
|
||||
for(unsigned int i=0;i<info.Limits.Generator.ports;i++) {
|
||||
write(":SENS:SA:SOUR"+QString::number(i)+":STAT OFF");
|
||||
}
|
||||
} else {
|
||||
// set the frequency
|
||||
write(":SENS:SA:SOUR"+QString::number(s.port)+":FREQ:CW "+QString::number(s.freq));
|
||||
// set the power
|
||||
write(":SENS:SA:SOUR"+QString::number(s.port)+":POW:VAL "+QString::number(s.dBm));
|
||||
// enable the port
|
||||
write(":SENS:SA:SOUR"+QString::number(s.port)+":STAT ON");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::setIdle(std::function<void (bool)> cb)
|
||||
{
|
||||
if(!connected) {
|
||||
return false;
|
||||
}
|
||||
if(!traceReaderStop()) {
|
||||
emit ConnectionLost();
|
||||
return false;
|
||||
}
|
||||
// traceTimer.stop();
|
||||
|
||||
write("*RST\r\n");
|
||||
if(cb) {
|
||||
cb(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList SNA5000ADriver::availableExtRefInSettings()
|
||||
{
|
||||
return {""};
|
||||
}
|
||||
|
||||
QStringList SNA5000ADriver::availableExtRefOutSettings()
|
||||
{
|
||||
return {""};
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::setExtRef(QString option_in, QString option_out)
|
||||
{
|
||||
Q_UNUSED(option_in)
|
||||
Q_UNUSED(option_out)
|
||||
return false;
|
||||
}
|
||||
|
||||
void SNA5000ADriver::write(QString s)
|
||||
{
|
||||
dataSocket.write(QString(s + "\r\n").toLocal8Bit());
|
||||
dataSocket.readAll();
|
||||
}
|
||||
|
||||
QString SNA5000ADriver::query(QString s, unsigned int timeout)
|
||||
{
|
||||
dataSocket.write(QString(s + "\r\n").toLocal8Bit());
|
||||
if(!waitForLine(timeout)) {
|
||||
return QString();
|
||||
} else {
|
||||
return QString(dataSocket.readLine());
|
||||
}
|
||||
}
|
||||
|
||||
long long SNA5000ADriver::queryInt(QString s)
|
||||
{
|
||||
auto resp = query(s);
|
||||
if(resp.isEmpty()) {
|
||||
return 0;
|
||||
} else {
|
||||
return resp.toLongLong();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> SNA5000ADriver::queryDoubleList(QString s)
|
||||
{
|
||||
std::vector<double> ret;
|
||||
auto resp = query(s, 1000);
|
||||
if(!resp.isEmpty()) {
|
||||
QStringList values = resp.split(",");
|
||||
for(auto v : values) {
|
||||
ret.push_back(v.toDouble());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SNA5000ADriver::extractTracePoints()
|
||||
{
|
||||
// while(connected) {
|
||||
// qDebug() << "SNA5000 Thread";
|
||||
// std::vector<double> xcoord = queryDoubleList(":CALC:DATA:XAXIS?");
|
||||
// std::map<QString, std::vector<double>> data;
|
||||
// for(auto i : excitedPorts) {
|
||||
// for(auto j : excitedPorts) {
|
||||
// QString name = "S"+QString::number(i)+QString::number(j);
|
||||
// std::vector<double> Sij = queryDoubleList(":SENS:DATA:RAWD? "+name);
|
||||
// if(Sij.size() != xcoord.size() * 2) {
|
||||
// // invalid size, abort
|
||||
// return;
|
||||
// }
|
||||
// data[name] = Sij;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Compile VNApoints
|
||||
// std::vector<VNAPoint> trace;
|
||||
// trace.resize(xcoord.size());
|
||||
// for(unsigned int i=0;i<xcoord.size();i++) {
|
||||
// trace[i].index = i;
|
||||
// trace[i].frequency = xcoord[i];
|
||||
// std::map<QString, std::complex<double>> tracedata;
|
||||
// for(auto d : data) {
|
||||
// tracedata[d.first] = std::complex(d.second[i*2], d.second[i*2+1]);
|
||||
// }
|
||||
// trace[i].data = tracedata;
|
||||
// }
|
||||
|
||||
// diffGen->newTrace(trace);
|
||||
// QThread::msleep(100);
|
||||
// }
|
||||
// traceTimer.start(500);
|
||||
}
|
||||
|
||||
void SNA5000ADriver::handleIncomingData()
|
||||
{
|
||||
if(!dataSocket.canReadLine()) {
|
||||
// no complete response yet, ignore
|
||||
return;
|
||||
}
|
||||
traceReader.waitingForResponse = false;
|
||||
std::vector<double> data;
|
||||
QStringList values = QString(dataSocket.readLine()).split(",");
|
||||
for(auto v : values) {
|
||||
data.push_back(v.toDouble());
|
||||
}
|
||||
if(traceReader.state == 0) {
|
||||
traceReader.xaxis = data;
|
||||
} else {
|
||||
unsigned int comp = 0;
|
||||
bool handled = false;
|
||||
for(auto i : excitedPorts) {
|
||||
for(auto j : excitedPorts) {
|
||||
comp++;
|
||||
if(traceReader.state == comp) {
|
||||
QString name = "S"+QString::number(i)+QString::number(j);
|
||||
traceReader.data[name] = data;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(handled) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(traceReader.state >= excitedPorts.size()*excitedPorts.size()) {
|
||||
// Check size, abort if wrong
|
||||
bool sizeOkay = true;
|
||||
for(auto d : traceReader.data) {
|
||||
if(d.second.size() != traceReader.xaxis.size() * 2) {
|
||||
sizeOkay = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(sizeOkay) {
|
||||
/*
|
||||
* The SNA5000A performs the measurements in multiple sweeps (with the stimulus active at one port per sweep).
|
||||
* E.g. measuring S11/S21 in the first sweep and then S12/S22. The LibreVNA-GUI expects each point of each sweep
|
||||
* to containg all measured parameters (S11/S12/S21/S22).
|
||||
*
|
||||
* Values that are not measured yet are reported as very small values by the SNA5000A. Ignore all datapoints
|
||||
* where at least one parameter is too small (=not measured yet). This will cause a delay in the displaying of
|
||||
* measurements but at least we can process complete datapoints afterwards.
|
||||
*/
|
||||
|
||||
// threshold equals -196dB, we can safely assume that no real measurement will ever below that
|
||||
constexpr double threshold = 1e-10;
|
||||
|
||||
int lastIndex = -1;
|
||||
for(unsigned int i=0;i<traceReader.xaxis.size();i++) {
|
||||
for(auto d : traceReader.data) {
|
||||
if(abs(d.second[i*2]) < threshold && abs(d.second[i*2+1]) < threshold) {
|
||||
lastIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(lastIndex >= 0) {
|
||||
// deteceted incomplete measurements
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(lastIndex == -1) {
|
||||
// all measurements complete
|
||||
lastIndex = traceReader.xaxis.size();
|
||||
}
|
||||
|
||||
if(lastIndex > 0) {
|
||||
// Compile VNApoints
|
||||
std::vector<VNAPoint> trace;
|
||||
trace.resize(lastIndex);
|
||||
for(int i=0;i<lastIndex;i++) {
|
||||
trace[i].index = i;
|
||||
trace[i].frequency = traceReader.xaxis[i];
|
||||
std::map<QString, std::complex<double>> tracedata;
|
||||
for(auto d : traceReader.data) {
|
||||
tracedata[d.first] = std::complex(d.second[i*2], d.second[i*2+1]);
|
||||
}
|
||||
trace[i].data = tracedata;
|
||||
}
|
||||
|
||||
diffGen->newTrace(trace);
|
||||
}
|
||||
}
|
||||
traceReader.state = 0;
|
||||
} else {
|
||||
// move on to next trace
|
||||
traceReader.state++;
|
||||
}
|
||||
traceReaderStatemachine();
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::traceReaderStop(unsigned int timeout)
|
||||
{
|
||||
traceReader.enabled = false;
|
||||
if(traceReader.waitingForResponse) {
|
||||
// already issued a command, needs to wait for the response parsing
|
||||
auto start = QDateTime::currentDateTimeUtc();
|
||||
while(traceReader.waitingForResponse) {
|
||||
if(start.msecsTo(QDateTime::currentDateTimeUtc()) >= timeout) {
|
||||
// timed out
|
||||
qWarning() << "Timed out waiting for trace reader to stop";
|
||||
return false;
|
||||
}
|
||||
QApplication::processEvents();
|
||||
}
|
||||
QObject::disconnect(&dataSocket, &QTcpSocket::readyRead, this, &SNA5000ADriver::handleIncomingData);
|
||||
return true;
|
||||
} else {
|
||||
// already stopped
|
||||
QObject::disconnect(&dataSocket, &QTcpSocket::readyRead, this, &SNA5000ADriver::handleIncomingData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void SNA5000ADriver::traceReaderRestart()
|
||||
{
|
||||
traceReader.enabled = true;
|
||||
traceReader.data.clear();
|
||||
traceReader.xaxis.clear();
|
||||
traceReader.state = 0;
|
||||
dataSocket.readAll();
|
||||
traceReaderStatemachine();
|
||||
}
|
||||
|
||||
void SNA5000ADriver::traceReaderStatemachine()
|
||||
{
|
||||
if(!traceReader.enabled) {
|
||||
return;
|
||||
}
|
||||
if(traceReader.state == 0) {
|
||||
write(":CALC:DATA:XAXIS?");
|
||||
traceReader.waitingForResponse = true;
|
||||
QObject::connect(&dataSocket, &QTcpSocket::readyRead, this, &SNA5000ADriver::handleIncomingData, Qt::UniqueConnection);
|
||||
} else {
|
||||
unsigned int comp = 0;
|
||||
for(auto i : excitedPorts) {
|
||||
for(auto j : excitedPorts) {
|
||||
comp++;
|
||||
if(traceReader.state == comp) {
|
||||
QString name = "S"+QString::number(i)+QString::number(j);
|
||||
write(":SENS:DATA:RAWD? "+name);
|
||||
traceReader.waitingForResponse = true;
|
||||
QObject::connect(&dataSocket, &QTcpSocket::readyRead, this, &SNA5000ADriver::handleIncomingData, Qt::UniqueConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SNA5000ADriver::waitForLine(unsigned int timeout)
|
||||
{
|
||||
auto start = QDateTime::currentDateTimeUtc();
|
||||
while(!dataSocket.canReadLine()) {
|
||||
if(start.msecsTo(QDateTime::currentDateTimeUtc()) >= timeout) {
|
||||
// timed out
|
||||
qWarning() << "Timed out waiting for response";
|
||||
return false;
|
||||
}
|
||||
dataSocket.waitForReadyRead(10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -0,0 +1,195 @@
|
||||
#ifndef SNA5000ADRIVER_H
|
||||
#define SNA5000ADRIVER_H
|
||||
|
||||
#include "../devicetcpdriver.h"
|
||||
|
||||
#include "../tracedifferencegenerator.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QTcpSocket>
|
||||
#include <QThread>
|
||||
|
||||
class SNA5000ADriver : public DeviceTCPDriver
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SNA5000ADriver();
|
||||
virtual ~SNA5000ADriver();
|
||||
|
||||
/**
|
||||
* @brief Returns the driver name. It must be unique across all implemented drivers and is used to identify the driver
|
||||
* @return driver name
|
||||
*/
|
||||
virtual QString getDriverName() override {return "SNA5000A";}
|
||||
/**
|
||||
* @brief Lists all available devices by their serial numbers
|
||||
* @return Serial numbers of detected devices
|
||||
*/
|
||||
virtual std::set<QString> GetAvailableDevices() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Connects to a device, given by its serial number
|
||||
*
|
||||
* @param serial Serial number of device that should be connected to
|
||||
* @return true if connection successful, otherwise false
|
||||
*/
|
||||
virtual bool connectTo(QString serial) override;
|
||||
/**
|
||||
* @brief Disconnects from device. Has no effect if no device was connected
|
||||
*/
|
||||
virtual void disconnect() override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the serial number of the connected device
|
||||
* @return Serial number of connected device (empty string if no device is connected)
|
||||
*/
|
||||
virtual QString getSerial() override {return serial;}
|
||||
|
||||
/**
|
||||
* @brief Returns the device information. This function will be called when a device has been connected. Its return value must be valid
|
||||
* directly after returning from DeviceDriver::connectTo()
|
||||
*
|
||||
* Emit the InfoUpdate() signal whenever the return value of this function changes.
|
||||
*
|
||||
* @return Device information
|
||||
*/
|
||||
virtual Info getInfo() override;
|
||||
|
||||
/**
|
||||
* @brief Returns a set of all active flags
|
||||
*
|
||||
* There is also a convenience function to check a specific flag, see DeviceDriver::asserted()
|
||||
*
|
||||
* @return Set of active flags
|
||||
*/
|
||||
virtual std::set<Flag> getFlags() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the device status string. It will be displayed in the status bar of the application
|
||||
*
|
||||
* Emit the StatusUpdated() signal whenever the return value of this function changes
|
||||
*
|
||||
* @return Status string
|
||||
*/
|
||||
virtual QString getStatus() override;
|
||||
|
||||
/**
|
||||
* @brief Names of available measurements.
|
||||
*
|
||||
* The names must be identical to the names used in the returned VNAMeasurement.
|
||||
* Typically the S parameters, e.g. this function may return {"S11","S12","S21","S22"} but any other names are also allowed.
|
||||
*
|
||||
* @return List of available VNA measurement parameters
|
||||
*/
|
||||
virtual QStringList availableVNAMeasurements() override;
|
||||
|
||||
/**
|
||||
* @brief Configures the VNA and starts a sweep
|
||||
* @param s VNA settings
|
||||
* @param cb Callback, must be called after the VNA has been configured
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setVNA(const VNASettings &s, std::function<void(bool)> cb = nullptr) override;
|
||||
|
||||
/**
|
||||
* @brief Names of available generator ports.
|
||||
*
|
||||
* Typically the port names, e.g. this function may return {"PORT1","PORT2"} but any other names are also allowed.
|
||||
*
|
||||
* @return List of available SA measurement parameters
|
||||
*/
|
||||
virtual QStringList availableSGPorts() override;
|
||||
/**
|
||||
* @brief Configures the generator
|
||||
* @param s Generator settings
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setSG(const SGSettings &s) override;
|
||||
|
||||
/**
|
||||
* @brief Sets the device to idle
|
||||
*
|
||||
* Stops all sweeps and signal generation
|
||||
*
|
||||
* @param cb Callback, must be called after the device has stopped all operations
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setIdle(std::function<void(bool)> cb = nullptr) override;
|
||||
|
||||
/**
|
||||
* @brief Returns the available options for the external reference input
|
||||
* @return External reference input options
|
||||
*/
|
||||
virtual QStringList availableExtRefInSettings() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the available options for the external reference output
|
||||
* @return External reference output options
|
||||
*/
|
||||
virtual QStringList availableExtRefOutSettings() override;
|
||||
|
||||
/**
|
||||
* @brief Configures the external reference input/output
|
||||
* @param option_in Reference input option (one of the options returned by availableExtRefInSettings())
|
||||
* @param option_out Reference output option (one of the options returned by availableExtRefOutSettings())
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setExtRef(QString option_in, QString option_out) override;
|
||||
|
||||
private slots:
|
||||
void extractTracePoints();
|
||||
signals:
|
||||
private slots:
|
||||
void handleIncomingData();
|
||||
private:
|
||||
struct {
|
||||
bool enabled;
|
||||
unsigned int state;
|
||||
std::vector<double> xaxis;
|
||||
std::map<QString, std::vector<double>> data;
|
||||
bool waitingForResponse;
|
||||
} traceReader;
|
||||
bool traceReaderStop(unsigned int timeout = 1000);
|
||||
void traceReaderRestart();
|
||||
void traceReaderStatemachine();
|
||||
bool waitForLine(unsigned int timeout);
|
||||
void write(QString s);
|
||||
QString query(QString s, unsigned int timeout = 100);
|
||||
long long queryInt(QString s);
|
||||
std::vector<double> queryDoubleList(QString s);
|
||||
QString serial;
|
||||
QTcpSocket dataSocket;
|
||||
|
||||
bool connected;
|
||||
Info info;
|
||||
|
||||
std::vector<int> excitedPorts;
|
||||
double excitationPower;
|
||||
|
||||
class VNAPoint {
|
||||
public:
|
||||
unsigned int index;
|
||||
double frequency;
|
||||
std::map<QString, std::complex<double>> data;
|
||||
bool operator==(const VNAPoint& rhs) {
|
||||
if(index != rhs.index || frequency != rhs.frequency || data.size() != rhs.data.size()) {
|
||||
return false;
|
||||
}
|
||||
if(data.size() == 0) {
|
||||
return true;
|
||||
} else {
|
||||
return std::prev(data.end())->second == std::prev(rhs.data.end())->second;
|
||||
}
|
||||
// return index == rhs.index && frequency == rhs.frequency && data.size() == rhs.data.size() && std::equal(data.begin(), data.end(), rhs.data.begin());
|
||||
}
|
||||
};
|
||||
|
||||
TraceDifferenceGenerator<VNAPoint> *diffGen;
|
||||
|
||||
std::map<QString, QHostAddress> detectedDevices;
|
||||
};
|
||||
|
||||
|
||||
#endif // SNA5000ADRIVER_H
|
@ -9,7 +9,7 @@
|
||||
SSA3000XDriver::SSA3000XDriver()
|
||||
: DeviceTCPDriver("SSA3000X")
|
||||
{
|
||||
diffGen = new TraceDifferenceGenerator<SpectrumPoint, 10>([=](const SpectrumPoint &p){
|
||||
diffGen = new TraceDifferenceGenerator<SpectrumPoint>([=](const SpectrumPoint &p){
|
||||
SAMeasurement m;
|
||||
m.pointNum = p.index;
|
||||
m.frequency = p.frequency;
|
||||
@ -128,7 +128,7 @@ bool SSA3000XDriver::connectTo(QString serial)
|
||||
info.Limits.SA.minFreq = 0;
|
||||
info.Limits.SA.maxFreq = maxFreq;
|
||||
info.Limits.SA.minRBW = 1;
|
||||
info.Limits.SA.maxRBW = 1000000;
|
||||
info.Limits.SA.maxRBW = 3000000;
|
||||
info.Limits.SA.mindBm = -20;
|
||||
info.Limits.SA.maxdBm = 0;
|
||||
info.Limits.Generator.ports = 1;
|
||||
|
@ -165,7 +165,7 @@ private:
|
||||
};
|
||||
|
||||
QTimer traceTimer;
|
||||
TraceDifferenceGenerator<SpectrumPoint, 10> *diffGen;
|
||||
TraceDifferenceGenerator<SpectrumPoint> *diffGen;
|
||||
|
||||
std::map<QString, QHostAddress> detectedDevices;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "LibreVNA/librevnausbdriver.h"
|
||||
#include "LibreVNA/Compound/compounddriver.h"
|
||||
#include "SSA3000X/ssa3000xdriver.h"
|
||||
#include "SNA5000A/sna5000adriver.h"
|
||||
|
||||
DeviceDriver *DeviceDriver::activeDriver = nullptr;
|
||||
|
||||
@ -23,6 +24,7 @@ std::vector<DeviceDriver *> DeviceDriver::getDrivers()
|
||||
ret.push_back(new LibreVNATCPDriver);
|
||||
ret.push_back(new CompoundDriver);
|
||||
ret.push_back(new SSA3000XDriver);
|
||||
ret.push_back(new SNA5000ADriver);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -110,26 +112,26 @@ DeviceDriver::Info::Info()
|
||||
hardware_version = "missing";
|
||||
Limits.VNA.ports = 2;
|
||||
Limits.VNA.minFreq = 0;
|
||||
Limits.VNA.maxFreq = 6000000000;
|
||||
Limits.VNA.maxFreq = 100000000000;
|
||||
Limits.VNA.mindBm = -100;
|
||||
Limits.VNA.maxdBm = 30;
|
||||
Limits.VNA.minIFBW = 1;
|
||||
Limits.VNA.maxIFBW = 1000000;
|
||||
Limits.VNA.maxIFBW = 100000000;
|
||||
Limits.VNA.maxPoints = 65535;
|
||||
|
||||
Limits.Generator.ports = 2;
|
||||
Limits.Generator.minFreq = 0;
|
||||
Limits.Generator.maxFreq = 6000000000;
|
||||
Limits.Generator.maxFreq = 100000000000;
|
||||
Limits.Generator.mindBm = -100;
|
||||
Limits.Generator.maxdBm = 30;
|
||||
|
||||
Limits.SA.ports = 2;
|
||||
Limits.SA.minFreq = 0;
|
||||
Limits.SA.maxFreq = 6000000000;
|
||||
Limits.SA.maxFreq = 100000000000;
|
||||
Limits.SA.mindBm = -100;
|
||||
Limits.SA.maxdBm = 30;
|
||||
Limits.SA.minRBW = 1;
|
||||
Limits.SA.maxRBW = 1000000;
|
||||
Limits.SA.maxRBW = 100000000;
|
||||
}
|
||||
|
||||
void DeviceDriver::Info::subset(const DeviceDriver::Info &info)
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
template<typename T, int minUnchanged>
|
||||
template<typename T>
|
||||
class TraceDifferenceGenerator {
|
||||
public:
|
||||
TraceDifferenceGenerator(std::function<void(const T&)> changeCallback) :
|
||||
@ -21,47 +21,92 @@ public:
|
||||
}
|
||||
|
||||
void newTrace(const std::vector<T> &trace) {
|
||||
if(trace.size() > last.size()) {
|
||||
// definitely got more points than last time. Find first point that is hasn't been transmitted and generate callbacks for it and all subsequent points
|
||||
unsigned int i=nextCallbackIndex;
|
||||
while(i < trace.size()) {
|
||||
callback(trace[i]);
|
||||
i++;
|
||||
}
|
||||
nextCallbackIndex = 0;
|
||||
} else if(trace.size() < last.size()) {
|
||||
if(trace.size() < last.size()) {
|
||||
// got less points than last time. This must be a completely new trace, generate callbacks for all points
|
||||
for(auto &i : trace) {
|
||||
callback(i);
|
||||
}
|
||||
nextCallbackIndex = 0;
|
||||
} else {
|
||||
// still the same amount of points.
|
||||
unsigned int i = nextCallbackIndex;
|
||||
unsigned int changedPoints = 0;
|
||||
// same amount or more points -> find first difference going backwards from next scheduled update point
|
||||
unsigned int lastDiff = nextCallbackIndex;
|
||||
do {
|
||||
if(i > 0) {
|
||||
i--;
|
||||
if(lastDiff > 0) {
|
||||
lastDiff--;
|
||||
} else {
|
||||
i = trace.size() - 1;
|
||||
}
|
||||
bool unchanged = last[i] == trace[i];
|
||||
if(!unchanged) {
|
||||
changedPoints = (i + trace.size() - nextCallbackIndex + 1);
|
||||
if(changedPoints > trace.size()) {
|
||||
changedPoints -= trace.size();
|
||||
// reached the beginning of the trace
|
||||
if(last.size() == trace.size()) {
|
||||
if(nextCallbackIndex == trace.size()) {
|
||||
// nothing has changed in the whole trace, abort
|
||||
break;
|
||||
} else {
|
||||
// wrap around to the end
|
||||
lastDiff = trace.size() - 1;
|
||||
}
|
||||
} else {
|
||||
// trace must be longer than last -> update all the way to the end
|
||||
lastDiff = trace.size() - 1;
|
||||
for(;nextCallbackIndex!=trace.size();nextCallbackIndex++) {
|
||||
callback(trace[nextCallbackIndex]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!(last[lastDiff] == trace[lastDiff])) {
|
||||
do {
|
||||
if(nextCallbackIndex >= trace.size()) {
|
||||
nextCallbackIndex = 0;
|
||||
}
|
||||
callback(trace[nextCallbackIndex]);
|
||||
nextCallbackIndex++;
|
||||
} while(nextCallbackIndex != lastDiff + 1);
|
||||
break;
|
||||
}
|
||||
} while (i != nextCallbackIndex);
|
||||
i = nextCallbackIndex;
|
||||
while(changedPoints--) {
|
||||
callback(trace[i]);
|
||||
i = (i + 1) % trace.size();
|
||||
}
|
||||
nextCallbackIndex = i;
|
||||
} while(lastDiff != nextCallbackIndex);
|
||||
}
|
||||
last = trace;
|
||||
// if(trace.size() > last.size()) {
|
||||
// // definitely got more points than last time. Find first point that hasn't been transmitted and generate callbacks for it and all subsequent points
|
||||
// unsigned int i=nextCallbackIndex;
|
||||
// while(i < trace.size()) {
|
||||
// callback(trace[i]);
|
||||
// i++;
|
||||
// }
|
||||
// nextCallbackIndex = trace.size();
|
||||
// } else i else {
|
||||
// // still the same amount of points.
|
||||
// unsigned int i = nextCallbackIndex;
|
||||
// unsigned int changedPoints = 0;
|
||||
// do {
|
||||
// if(i > 0) {
|
||||
// i--;
|
||||
// } else {
|
||||
// // reached the end
|
||||
// if(nextCallbackIndex == trace.size()) {
|
||||
// // checked the whole trace
|
||||
// break;
|
||||
// } else {
|
||||
// // last callback was within trace, wrap around
|
||||
// i = trace.size() - 1;
|
||||
// }
|
||||
// }
|
||||
// bool unchanged = last[i] == trace[i];
|
||||
// if(!unchanged) {
|
||||
// changedPoints = (i + trace.size() - nextCallbackIndex + 1);
|
||||
// if(changedPoints > trace.size()) {
|
||||
// changedPoints -= trace.size();
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// } while (i != nextCallbackIndex);
|
||||
// i = nextCallbackIndex;
|
||||
// while(changedPoints--) {
|
||||
// callback(trace[i]);
|
||||
// i = (i + 1) % trace.size();
|
||||
// }
|
||||
// nextCallbackIndex = i;
|
||||
// }
|
||||
// last = trace;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -189,7 +189,8 @@ void SignalgeneratorWidget::deviceInfoUpdated()
|
||||
delete cb;
|
||||
}
|
||||
portCheckboxes.clear();
|
||||
for(unsigned int i=1;i<=DeviceDriver::getInfo(window->getDevice()).Limits.Generator.ports;i++) {
|
||||
auto info = DeviceDriver::getInfo(window->getDevice());
|
||||
for(unsigned int i=1;i<=info.Limits.Generator.ports;i++) {
|
||||
auto cb = new QCheckBox("Port "+QString::number(i));
|
||||
ui->portBox->layout()->addWidget(cb);
|
||||
portCheckboxes.push_back(cb);
|
||||
@ -207,6 +208,11 @@ void SignalgeneratorWidget::deviceInfoUpdated()
|
||||
});
|
||||
}
|
||||
setPort(port);
|
||||
|
||||
ui->levelSlider->setMaximum(info.Limits.Generator.maxdBm * 100);
|
||||
ui->levelSlider->setMinimum(info.Limits.Generator.mindBm * 100);
|
||||
ui->levelSpin->setMaximum(info.Limits.Generator.maxdBm);
|
||||
ui->levelSpin->setMinimum(info.Limits.Generator.mindBm);
|
||||
}
|
||||
|
||||
void SignalgeneratorWidget::setLevel(double level)
|
||||
|
@ -36,6 +36,7 @@ HEADERS += \
|
||||
Device/LibreVNA/manualcontroldialogvff.h \
|
||||
Device/LibreVNA/receivercaldialog.h \
|
||||
Device/LibreVNA/sourcecaldialog.h \
|
||||
Device/SNA5000A/sna5000adriver.h \
|
||||
Device/SSA3000X/ssa3000xdriver.h \
|
||||
Device/devicedriver.h \
|
||||
Device/devicelog.h \
|
||||
@ -193,6 +194,7 @@ SOURCES += \
|
||||
Device/LibreVNA/manualcontroldialogvff.cpp \
|
||||
Device/LibreVNA/receivercaldialog.cpp \
|
||||
Device/LibreVNA/sourcecaldialog.cpp \
|
||||
Device/SNA5000A/sna5000adriver.cpp \
|
||||
Device/SSA3000X/ssa3000xdriver.cpp \
|
||||
Device/devicedriver.cpp \
|
||||
Device/devicelog.cpp \
|
||||
|
@ -39,6 +39,7 @@ void TraceModel::addTrace(Trace *t)
|
||||
endInsertRows();
|
||||
t->setModel(this);
|
||||
emit traceAdded(t);
|
||||
emit requiredExcitation();
|
||||
}
|
||||
|
||||
void TraceModel::removeTrace(unsigned int index)
|
||||
@ -50,6 +51,7 @@ void TraceModel::removeTrace(unsigned int index)
|
||||
traces.erase(traces.begin() + index);
|
||||
endRemoveRows();
|
||||
emit traceRemoved(trace);
|
||||
emit requiredExcitation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,9 +401,6 @@ bool AppWindow::ConnectToDevice(QString serial, DeviceDriver *driver)
|
||||
break;
|
||||
}
|
||||
}
|
||||
for(auto m : modeHandler->getModes()) {
|
||||
connect(device, &DeviceDriver::InfoUpdated, m, &Mode::deviceInfoUpdated);
|
||||
}
|
||||
|
||||
// vdevice->initialize();
|
||||
|
||||
@ -1135,6 +1132,9 @@ void AppWindow::DeviceInfoUpdated()
|
||||
modeHandler->getActiveMode()->initializeDevice();
|
||||
}
|
||||
UpdateReferenceToolbar();
|
||||
for(auto m : modeHandler->getModes()) {
|
||||
m->deviceInfoUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
//void AppWindow::SourceCalibrationDialog()
|
||||
|
@ -41,6 +41,7 @@ SOURCES += \
|
||||
../LibreVNA-GUI/Device/LibreVNA/Compound/compounddriver.cpp \
|
||||
../LibreVNA-GUI/Device/LibreVNA/Compound/compounddeviceeditdialog.cpp \
|
||||
../LibreVNA-GUI/Device/SSA3000X/ssa3000xdriver.cpp \
|
||||
../LibreVNA-GUI/Device/SNA5000A/sna5000adriver.cpp \
|
||||
../LibreVNA-GUI/Device/devicedriver.cpp \
|
||||
../LibreVNA-GUI/Device/devicelog.cpp \
|
||||
../LibreVNA-GUI/Device/LibreVNA/devicepacketlog.cpp \
|
||||
@ -216,6 +217,7 @@ HEADERS += \
|
||||
../LibreVNA-GUI/Device/LibreVNA/Compound/compounddriver.h \
|
||||
../LibreVNA-GUI/Device/LibreVNA/Compound/compounddeviceeditdialog.h \
|
||||
../LibreVNA-GUI/Device/SSA3000X/ssa3000xdriver.h \
|
||||
../LibreVNA-GUI/Device/SNA5000A/sna5000adriver.h \
|
||||
../LibreVNA-GUI/Device/devicedriver.h \
|
||||
../LibreVNA-GUI/Device/devicelog.h \
|
||||
../LibreVNA-GUI/Device/LibreVNA/devicepacketlog.h \
|
||||
|
Loading…
Reference in New Issue
Block a user