Added SCPI command for touchstone file format
This commit is contained in:
parent
68dc9842e9
commit
3176037c5d
Binary file not shown.
@ -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}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user