LibreVNA/Software/PC_Application/LibreVNA-GUI/Device/SNA5000A/sna5000adriver.cpp

547 lines
16 KiB
C++
Raw Normal View History

2023-11-01 01:49:08 +08:00
#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;
}