Added SCPI command for touchstone file format

This commit is contained in:
Jan Käberich 2021-09-07 22:49:51 +02:00
parent 68dc9842e9
commit 3176037c5d
5 changed files with 132 additions and 38 deletions

View File

@ -422,6 +422,30 @@ Depending on the sweep and possible confiigured math operations, x may be either
-0.0458452,-0.028729
\end{example}
\subsubsection{VNA:TRACe:TOUCHSTONE}
\query{Returns the content of multiple trace according to the touchstone format}{VNA:TRACe:TOUCHSTONE?}{<trace1>,<trace2>,<trace3>,...}{Touchstone file content in ASCII}
Some additional constraints apply:
\begin{itemize}
\item The number of specified traces must be a square number. The number of ports in the touchstone file is inferred from that.
\item Only frequency domain traces are allowed.
\item All traces must have the same number of points and the same start/stop frequency.
\item The order in which the traces are specified matters and depending on its index and each trace must be a reflection or transmission measurement:
\begin{itemize}
\item Assuming that $n$ is the number of ports of the desired touchstone file, the $n*n$ number of traces must be specified in this order:
$$ S_{11}...S_{1n},S_{21}...S_{2n},...,S_{n1}...S_{nn} $$
\item For every trace $S_{ij}$, the trace must contain a reflection measurement if $i=j$ and a transmission measurement if $i\neq j$.
\end{itemize}
\item Traces can be specified either by name or by index.
\item A deviation from any of these points (invalid number of traces, non-existing trace, wrong order, ...) will result in an error being returned.
\end{itemize}
\begin{example}
:VNA:TRACE:TOUCHSTONE? S11 S12 S21 S22
# GHZ S RI R 50
1.000000000000 1.000497817993 0.010679213330 0.000013886895 -0.000054684886 -0.000023392624 -0.000021111371 0.401717424393 0.702864229679
1.002000000000 1.000323534012 0.010577851906 -0.000011075452 -0.000013504875 0.000000477609 -0.000007789199 0.413144201040 0.696514129639
...
\end{example}
\subsubsection{VNA:TRACe:MAXFrequency}
\query{Returns the highest frequency contained in the trace}{VNA:TRACe:MAXFrequency?}{<trace>, either by name or by index}{maximum frequency in Hz}

View File

@ -141,13 +141,10 @@ void TraceWidget::on_view_clicked(const QModelIndex &index)
void TraceWidget::SetupSCPI()
{
auto findTrace = [=](QStringList params) -> Trace* {
if(params.size() < 1) {
return nullptr;
}
auto findTraceFromName = [=](QString name) -> Trace* {
// check if trace is specified by number
bool ok;
auto n = params[0].toUInt(&ok);
auto n = name.toUInt(&ok);
if(ok) {
// check if enough traces exist
if(n < model.getTraces().size()) {
@ -159,7 +156,7 @@ void TraceWidget::SetupSCPI()
} else {
// trace specified by name
for(auto t : model.getTraces()) {
if(t->name().compare(params[0], Qt::CaseInsensitive) == 0) {
if(t->name().compare(name, Qt::CaseInsensitive) == 0) {
return t;
}
}
@ -167,6 +164,12 @@ void TraceWidget::SetupSCPI()
return nullptr;
}
};
auto findTrace = [=](QStringList params) -> Trace* {
if(params.size() < 1) {
return nullptr;
}
return findTraceFromName(params[0]);
};
auto createStringFromData = [](Trace *t, const Trace::Data &d) -> QString {
if(Trace::isSAParamater(t->liveParameter())) {
@ -220,6 +223,63 @@ void TraceWidget::SetupSCPI()
}
}
}));
add(new SCPICommand("TOUCHSTONE", nullptr, [=](QStringList params) -> QString {
if(params.size() < 1) {
// no traces given
return "ERROR";
}
// check number of paramaters, must be a square number
int numTraces = params.size();
int ports = round(sqrt(numTraces));
if(ports * ports != numTraces) {
// invalid number of traces
return "ERROR";
}
Trace* traces[numTraces];
for(int i=0;i<numTraces;i++) {
traces[i] = findTraceFromName(params[i]);
if(!traces[i]) {
// couldn't find that trace
return "ERROR";
}
}
// check if trace selection is valid
auto npoints = traces[0]->size();
auto f_start = traces[0]->minX();
auto f_stop = traces[0]->maxX();
for(int i=0;i<ports;i++) {
for(int j=0;j<ports;j++) {
bool need_reflection = i==j;
auto t = traces[j+i*ports];
if(t->getDataType() != Trace::DataType::Frequency) {
// invalid domain
return "ERROR";
}
if(t->isReflection() != need_reflection) {
// invalid measurement at this position
return "ERROR";
}
if((t->size() != npoints) || (t->minX() != f_start) || (t->maxX() != f_stop)) {
// frequency points are not identical
return "ERROR";
}
}
}
// all traces checked, they are valid.
// Constructing touchstone
Touchstone t = Touchstone(ports);
for(unsigned int i=0;i<npoints;i++) {
Touchstone::Datapoint d;
d.frequency = traces[0]->getSample(i).x;
for(auto trace : traces) {
d.S.push_back(trace->getSample(i).y);
}
t.AddDatapoint(d);
}
// touchstone assembled, save to dummyfile
auto s = t.toString(Touchstone::Scale::GHz, Touchstone::Format::RealImaginary);
return QString::fromStdString(s.str());
}));
add(new SCPICommand("MAXFrequency", nullptr, [=](QStringList params) -> QString {
auto t = findTrace(params);
if(!t) {

View File

@ -7,6 +7,7 @@
#include <cctype>
#include <string>
#include "Util/util.h"
#include <QDebug>
using namespace std;
@ -45,25 +46,34 @@ void Touchstone::toFile(string filename, Scale unit, Format format)
// create file
ofstream file;
file.open(filename);
file << std::fixed << std::setprecision(12);
file << toString(unit, format).rdbuf();
file.close();
this->filename = QString::fromStdString(filename);
}
stringstream Touchstone::toString(Touchstone::Scale unit, Touchstone::Format format)
{
stringstream s;
s << std::fixed << std::setprecision(12);
// write option line
file << "# ";
s << "# ";
switch(unit) {
case Scale::Hz: file << "HZ "; break;
case Scale::kHz: file << "KHZ "; break;
case Scale::MHz: file << "MHZ "; break;
case Scale::GHz: file << "GHZ "; break;
case Scale::Hz: s << "HZ "; break;
case Scale::kHz: s << "KHZ "; break;
case Scale::MHz: s << "MHZ "; break;
case Scale::GHz: s << "GHZ "; break;
}
// only S parameters supported so far
file << "S ";
s << "S ";
switch(format) {
case Format::DBAngle: file << "DB "; break;
case Format::RealImaginary: file << "RI "; break;
case Format::MagnitudeAngle: file << "MA "; break;
case Format::DBAngle: s << "DB "; break;
case Format::RealImaginary: s << "RI "; break;
case Format::MagnitudeAngle: s << "MA "; break;
}
// reference impedance is always 50 ohm
file << "R 50\n";
s << "R 50\n";
auto printParameter = [format](ostream &out, complex<double> &c) {
switch (format) {
@ -81,45 +91,44 @@ void Touchstone::toFile(string filename, Scale unit, Format format)
for(auto p : m_datapoints) {
switch(unit) {
case Scale::Hz: file << p.frequency; break;
case Scale::kHz: file << p.frequency / 1e3; break;
case Scale::MHz: file << p.frequency / 1e6; break;
case Scale::GHz: file << p.frequency / 1e9; break;
case Scale::Hz: s << p.frequency; break;
case Scale::kHz: s << p.frequency / 1e3; break;
case Scale::MHz: s << p.frequency / 1e6; break;
case Scale::GHz: s << p.frequency / 1e9; break;
}
file << " ";
s << " ";
// special cases for 1 and 2 port
if (m_ports == 1) {
printParameter(file, p.S[0]);
file << "\n";
printParameter(s, p.S[0]);
s << "\n";
} else if (m_ports == 2){
printParameter(file, p.S[0]);
printParameter(s, p.S[0]);
// touchstone expects S11 S21 S12 S22 order, swap S12 and S21
file << " ";
printParameter(file, p.S[2]);
file << " ";
printParameter(file, p.S[1]);
file << " ";
printParameter(file, p.S[3]);
file << "\n";
s << " ";
printParameter(s, p.S[2]);
s << " ";
printParameter(s, p.S[1]);
s << " ";
printParameter(s, p.S[3]);
s << "\n";
} else {
// print parameters in matrix form
for(unsigned int i=0;i<m_ports;i++) {
for(unsigned int j=0;j<m_ports;j++) {
printParameter(file, p.S[i*m_ports + j]);
printParameter(s, p.S[i*m_ports + j]);
if (j%4 == 3) {
file << "\n";
s << "\n";
} else {
file << " ";
s << " ";
}
}
if(m_ports%4 != 0) {
file << "\n";
s << "\n";
}
}
}
}
file.close();
this->filename = QString::fromStdString(filename);
return s;
}
Touchstone Touchstone::fromFile(string filename)

View File

@ -31,6 +31,7 @@ public:
Touchstone(unsigned int m_ports);
void AddDatapoint(Datapoint p);
void toFile(std::string filename, Scale unit = Scale::GHz, Format format = Format::RealImaginary);
std::stringstream toString(Scale unit = Scale::GHz, Format format = Format::RealImaginary);
static Touchstone fromFile(std::string filename);
double minFreq();
double maxFreq();