Improve de-embedding

- Add Z parameters with general conversion from and to S parameters
- Implement impedance renormalization over general Z parameters
- Fix crash after taking de-embedding measurement
- Fix various small bugs with new parameter implementation
This commit is contained in:
Jan Käberich 2025-02-21 12:37:39 +01:00
parent 77a3fc5039
commit 0205ab494d
11 changed files with 286 additions and 77 deletions

View file

@ -429,7 +429,7 @@ void CalibrationMeasurement::TwoPort::addPoint(const DeviceDriver::VNAMeasuremen
{
Point p;
p.frequency = m.frequency;
p.S = m.toSparam(port1, port2);
p.S = m.toSparam().reduceTo({port1, port2});
points.push_back(p);
timestamp = QDateTime::currentDateTimeUtc();
}

View file

@ -61,33 +61,55 @@ unsigned int DeviceDriver::SApoints() {
}
}
Sparam DeviceDriver::VNAMeasurement::toSparam(int port1, int port2) const
Sparam DeviceDriver::VNAMeasurement::toSparam(int ports) const
{
Sparam S;
S.set(1,1, measurements.at("S"+QString::number(port1)+QString::number(port1)));
S.set(1,2, measurements.at("S"+QString::number(port1)+QString::number(port2)));
S.set(2,1, measurements.at("S"+QString::number(port2)+QString::number(port1)));
S.set(2,2, measurements.at("S"+QString::number(port2)+QString::number(port2)));
if(ports == 0) {
// determine number of ports by highest available S parameter
for(const auto &m : measurements) {
if(!m.first.startsWith("S")) {
// something else we can not handle
continue;
}
int to = m.first.mid(1,1).toUInt();
int from = m.first.mid(2,1).toUInt();
if(to > ports) {
ports = to;
}
if(from > ports) {
ports = from;
}
}
}
// create S paramters
auto S = Sparam(ports);
// fill data
for(const auto &m : measurements) {
if(!m.first.startsWith("S")) {
// something else we can not handle
continue;
}
int to = m.first.mid(1,1).toUInt();
int from = m.first.mid(2,1).toUInt();
S.set(to, from, m.second);
}
return S;
}
void DeviceDriver::VNAMeasurement::fromSparam(Sparam S, int port1, int port2)
void DeviceDriver::VNAMeasurement::fromSparam(Sparam S, std::vector<unsigned int> portMapping)
{
QString s11 = "S"+QString::number(port1)+QString::number(port1);
QString s12 = "S"+QString::number(port1)+QString::number(port2);
QString s21 = "S"+QString::number(port2)+QString::number(port1);
QString s22 = "S"+QString::number(port2)+QString::number(port2);
if(measurements.count(s11)) {
measurements[s11] = S.get(1,1);
if(portMapping.size() == 0) {
// set up default port mapping
for(unsigned int i=1;i<=S.ports();i++) {
portMapping.push_back(i);
}
}
if(measurements.count(s12)) {
measurements[s12] = S.get(1,2);
}
if(measurements.count(s21)) {
measurements[s21] = S.get(2,1);
}
if(measurements.count(s22)) {
measurements[s22] = S.get(2,2);
for(unsigned int i=0;i<portMapping.size();i++) {
for(unsigned int j=0;j<portMapping.size();j++) {
QString name = "S"+QString::number(i+1)+QString::number(j+1);
if(measurements.count(name)) {
measurements[name] = S.get(portMapping[i], portMapping[j]);
}
}
}
}

View file

@ -296,8 +296,20 @@ public:
// Value: complex measurement in real/imag (linear, not in dB)
std::map<QString, std::complex<double>> measurements;
Sparam toSparam(int port1, int port2) const;
void fromSparam(Sparam S, int port1, int port2);
Sparam toSparam(int ports = 0) const;
/* Sets the measurement values in the VNAmeasurement (if existent) to the values from the S parameter matrix.
* The portMapping parameter can be used to specify which values to set from which S parameter:
* Example: S parameter contains 4 port S parameters, but the VNAmeasurement is 2 port only with this mapping:
* VNAMeasurement port | S parameter port
* --------------------|-----------------
* 1 | 2
* 2 | 4
* This means that we want S22 (from the 4 port S parameter) stored as S11 (in the VNAMeasurement).
* Function call for this example: fromSparam(S, {2,4})
*
* If no portMapping is specified, the port order (and mapping) from the S paramters are kept.
*/
void fromSparam(Sparam S, std::vector<unsigned int> portMapping = {});
VNAMeasurement interpolateTo(const VNAMeasurement &to, double a);
};

View file

@ -24,18 +24,52 @@ Sparam::Sparam(const ABCDparam &a, Type Z0)
{
}
Sparam::Sparam(const Zparam &Z, std::vector<Type> Z0n)
{
if(Z.ports() != Z0n.size()) {
throw std::runtime_error("number of supplied characteristic impedances does not match number of ports");
}
/* general formula for converting S parameters to Z parameters:
* S = (sqrt(y)*Z*sqrt(y)-1)*(sqrt(y)*Z*sqrt(y)+1)^-1
* with:
* Z = Z parameter matrix
* 1 = identity matrix
* sqrt(y) = diagonal matrix with the root of characteristic admittances as it non-zero elements
*/
// create identity matrix
auto ident = Eigen::MatrixXcd::Identity(Z.ports(), Z.ports());
// create sqrt(y) matrix
Eigen::MatrixXcd sqrty = Eigen::MatrixXcd::Zero(Z.ports(), Z.ports());
// fill with characteristic admittance
for(unsigned int i=0;i<Z.ports();i++) {
sqrty(i, i) = 1.0/(sqrt(Z0n[i]));
}
// apply formula
auto yZy = sqrty*Z.data*sqrty;
data = (yZy-ident)*(yZy+ident).inverse();
}
Sparam::Sparam(const Zparam &Z, Type Z0)
: Sparam(Z, std::vector<Type>(Z.ports(), Z0))
{
}
void Sparam::swapPorts(unsigned int p1, unsigned int p2)
{
// swap columns
data.col(p1-1).swap(data.col(p2-1));
data.row(p1-1).swap(data.row(p2-1));
// auto cbuf = data.col(p1-1);
// data.col(p1-1) = data.col(p2-1);
// data.col(p2-1) = cbuf;
// // swap rows
// auto rbuf = data.row(p1-1);
// data.row(p1-1) = data.row(p2-1);
// data.row(p2-1) = rbuf;
}
Sparam Sparam::reduceTo(std::vector<unsigned int> ports) const
{
auto ret = Sparam(ports.size());
for(unsigned int from=0;from<ports.size();from++) {
for(unsigned int to=0;to<ports.size();to++) {
ret.data(to, from) = get(ports[to], ports[from]);
}
}
return ret;
}
ABCDparam::ABCDparam(const Sparam &s, Type Z01, Type Z02)
@ -68,6 +102,12 @@ ABCDparam::ABCDparam(const Sparam &s, Type Z0)
{
}
Parameters::Parameters(Type m11)
: Parameters(1)
{
data(0, 0) = m11;
}
Parameters::Parameters(Type m11, Type m12, Type m21, Type m22)
: Parameters(2)
{
@ -130,7 +170,7 @@ Yparam::Yparam(const Sparam &s, Type Z01, Type Z02)
{
// TODO can this be done for any number of ports
if(s.ports() != 2) {
throw std::runtime_error("Can only create ABCD parameter from 2 port S parameters");
throw std::runtime_error("Can only create Y parameter from 2 port S parameters");
}
data = Eigen::MatrixXcd(2,2);
// from https://www.rfcafe.com/references/electrical/s-h-y-z.htm
@ -145,3 +185,33 @@ Yparam::Yparam(const Sparam &s, Type Z0)
: Yparam(s, Z0, Z0)
{
}
Zparam::Zparam(const Sparam &S, std::vector<Type> Z0n)
{
if(S.ports() != Z0n.size()) {
throw std::runtime_error("number of supplied characteristic impedances does not match number of ports");
}
/* general formula for converting S parameters to Z parameters:
* Z = sqrt(z)*(1+S)*(1-S)^-1*sqrt(z)
* with:
* S = S parameter matrix
* 1 = identity matrix
* sqrt(z) = diagonal matrix with the root of characteristic impedances as it non-zero elements
*/
// create identity matrix
auto ident = Eigen::MatrixXcd::Identity(S.ports(), S.ports());
// create sqrt(z) matrix
Eigen::MatrixXcd sqrtz = Eigen::MatrixXcd::Zero(S.ports(), S.ports());
// fill with characteristic impedance
for(unsigned int i=0;i<S.ports();i++) {
sqrtz(i, i) = sqrt(Z0n[i]);
}
// apply formula
data = sqrtz*(ident+S.data)*(ident-S.data).inverse()*sqrtz;
}
Zparam::Zparam(const Sparam &S, Type Z0)
: Zparam(S, std::vector<Type>(S.ports(), Z0))
{
}

View file

@ -11,6 +11,7 @@ using Type = std::complex<double>;
class Parameters : public Savable {
public:
Parameters(Type m11);
Parameters(Type m11, Type m12, Type m21, Type m22);
Parameters(int num_ports);
Parameters() : Parameters(2){}
@ -29,6 +30,7 @@ public:
// forward declaration of parameter classes
class Sparam;
class Zparam;
class Tparam;
class ABCDparam;
@ -38,6 +40,8 @@ public:
Sparam(const Tparam &t);
Sparam(const ABCDparam &a, Type Z01, Type Z02);
Sparam(const ABCDparam &a, Type Z0);
Sparam(const Zparam &Z, std::vector<Type> Z0n);
Sparam(const Zparam &Z, Type Z0);
Sparam operator+(const Sparam &r) const {
Sparam p(ports());
p.data = data+r.data;
@ -49,6 +53,19 @@ public:
return p;
}
void swapPorts(unsigned int p1, unsigned int p2);
// reduces the S parameter matrix to specified ports.
// Example: 4 port S parameters as an input but we want the 2 port data from the original ports 1 and 3
// Call: S.reduceTo(1, 3)
// Result: 2 port S parameters (S11, S12, S21, S22) which are set to the original (S11, S13, S31, S33)
Sparam reduceTo(std::vector<unsigned int> ports) const;
};
class Zparam : public Parameters {
public:
using Parameters::Parameters;
Zparam(int num_ports) : Parameters(num_ports){}
Zparam(const Sparam &S, std::vector<Type> Z0n);
Zparam(const Sparam &S, Type Z0);
};
class ABCDparam : public Parameters {
@ -66,7 +83,7 @@ public:
ABCDparam inverse() {
ABCDparam i;
// by hand, this is faster because the Eigen matrix is using dynamic size
Type det = data(0,0)*data(1,1) - data(0,1)*data(2,1);
Type det = data(0,0)*data(1,1) - data(0,1)*data(1,0);
i.data(0,0) = data(1,1) / det;
i.data(0,1) = -data(0,1) / det;
i.data(1,0) = -data(1,0) / det;
@ -87,7 +104,7 @@ public:
ABCDparam r = *this;
r.data(0,0) += s;
r.data(1,1) += s;
r = r * (1.0/t);
r.data = r.data * (1.0/t);
return r;
}
};
@ -109,7 +126,7 @@ public:
}
Tparam inverse() {
Tparam i;
Type det = data(0,0)*data(1,1) - data(0,1)*data(2,1);
Type det = data(0,0)*data(1,1) - data(0,1)*data(1,0);
i.data(0,0) = data(1,1) / det;
i.data(0,1) = -data(0,1) / det;
i.data(1,0) = -data(1,0) / det;

View file

@ -25,7 +25,8 @@ void Deembedding::measurementCompleted()
measuringOption = nullptr;
}
delete measurementDialog;
measurementDialog->close();
measurementDialog->deleteLater();
measurementDialog = nullptr;
measurementUI = nullptr;
}
@ -37,7 +38,7 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option)
auto ui = new Ui_DeembeddingMeasurementDialog;
measurementUI = ui;
ui->setupUi(measurementDialog);
connect(measurementDialog, &QDialog::finished, [=](){
connect(measurementDialog, &QDialog::finished, this, [=](){
if(measuring) {
measuring = false;
emit finishedMeasurement();
@ -53,7 +54,7 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option)
connect(traceChooser, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled);
connect(ui->bMeasure, &QPushButton::clicked, [=](){
connect(ui->bMeasure, &QPushButton::clicked, this, [=](){
ui->bMeasure->setEnabled(false);
traceChooser->setEnabled(false);
ui->buttonBox->setEnabled(false);
@ -61,7 +62,7 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option)
emit triggerMeasurement();
});
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, [=](){
// create datapoints from individual traces
measurements.clear();
auto points = Trace::assembleDatapoints(traceChooser->getTraces());

View file

@ -37,36 +37,10 @@ std::set<unsigned int> ImpedanceRenormalization::getAffectedPorts()
void ImpedanceRenormalization::transformDatapoint(DeviceDriver::VNAMeasurement &p)
{
std::map<QString, std::complex<double>> transformed;
int ports = 0;
QString name = "S11";
while(p.measurements.count(name) > 0) {
ports++;
name = "S"+QString::number(ports+1)+QString::number(ports+1);
}
for(auto i=1;i<=ports;i++) {
// handle reflection parameters
auto S11name = "S"+QString::number(i)+QString::number(i);
auto S11 = p.measurements[S11name];
transformed[S11name] = Util::ImpedanceToSparam(Util::SparamToImpedance(S11, p.Z0), impedance);
// handle transmission parameters
for(auto j=i+1;j<=ports;j++) {
auto S12name = "S"+QString::number(i)+QString::number(j);
auto S21name = "S"+QString::number(j)+QString::number(i);
auto S22name = "S"+QString::number(j)+QString::number(j);
if(!p.measurements.count(S12name) || !p.measurements.count(S21name) || !p.measurements.count(S22name)) {
// not all measurements available, skip this
continue;
}
auto S12 = p.measurements[S12name];
auto S21 = p.measurements[S21name];
auto S22 = p.measurements[S22name];
auto S_t = Sparam(ABCDparam(Sparam(S11, S12, S21, S22), p.Z0), impedance);
transformed[S12name] = S_t.get(1,2);
transformed[S21name] = S_t.get(2,1);
}
}
p.measurements = transformed;
auto S = p.toSparam();
auto Z = Zparam(S, p.Z0);
auto S_renorm = Sparam(Z, impedance);
p.fromSparam(S_renorm);
p.Z0 = impedance;
}

View file

@ -114,6 +114,9 @@ void MatchingNetwork::transformDatapoint(DeviceDriver::VNAMeasurement &p)
// handle the measurements
for(auto &meas : p.measurements) {
QString name = meas.first;
if(!name.startsWith("S")) {
continue;
}
unsigned int i = name.mid(1,1).toUInt();
unsigned int j = name.mid(2,1).toUInt();
if(i == j) {
@ -126,9 +129,9 @@ void MatchingNetwork::transformDatapoint(DeviceDriver::VNAMeasurement &p)
} else {
// another reflection measurement
try {
auto S = uncorrected.toSparam(i, port);
auto S = uncorrected.toSparam().reduceTo({i, port});
auto corrected = Sparam(ABCDparam(S, p.Z0) * m.reverse, p.Z0);
p.fromSparam(corrected, i, port);
p.fromSparam(corrected, {i, port});
} catch (...) {
// missing measurements, nothing can be done
}

View file

@ -29,7 +29,7 @@ void TwoThru::transformDatapoint(DeviceDriver::VNAMeasurement &p)
{
// correct measurement
if(points.size() > 0) {
Tparam meas(p.toSparam(port1,port2));
Tparam meas(p.toSparam().reduceTo({port1, port2}));
Tparam inv1, inv2;
if(p.frequency < points.front().freq) {
@ -59,7 +59,7 @@ void TwoThru::transformDatapoint(DeviceDriver::VNAMeasurement &p)
// perform correction
Tparam corrected = inv1*meas*inv2;
// transform back into S parameters
p.fromSparam(Sparam(corrected), port1, port2);
p.fromSparam(Sparam(corrected), {port1, port2});
}
}
@ -280,7 +280,7 @@ std::vector<TwoThru::Point> TwoThru::calculateErrorBoxes(std::vector<DeviceDrive
// ignore possible DC point
continue;
}
auto S = m.toSparam(port1, port2);
auto S = m.toSparam().reduceTo({port1, port2});
S11.push_back(S.get(1,1));
S12.push_back(S.get(1,2));
S21.push_back(S.get(2,1));
@ -466,13 +466,13 @@ std::vector<TwoThru::Point> TwoThru::calculateErrorBoxes(std::vector<DeviceDrive
vector<Sparam> p;
vector<double> f;
for(auto d : data_2xthru) {
p.push_back(d.toSparam(1, 2));
p.push_back(d.toSparam().reduceTo({port1, port2}));
f.push_back(d.frequency);
}
auto data_2xthru_Sparam = p;
vector<Sparam> data_fix_dut_fix_Sparam;
for(auto d : data_fix_dut_fix) {
data_fix_dut_fix_Sparam.push_back(d.toSparam(1, 2));
data_fix_dut_fix_Sparam.push_back(d.toSparam().reduceTo({port1, port2}));
}
// grabbing S21

View file

@ -61,3 +61,109 @@ void ParameterTests::ABCD2S()
QVERIFY(qFuzzyCompare(s.get(2,2).real(), S22.real()));
QVERIFY(qFuzzyCompare(s.get(2,2).imag(), S22.imag()));
}
void ParameterTests::S2Z_1P()
{
using namespace std::complex_literals;
std::complex<double> S11 = 0.0038 + 0.0248i;
auto S = Sparam(S11);
// test for various characteristic impedances
for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) {
auto Z = Zparam(S, Z0);
// calculate expected Z values based on two-port formulas
auto Z11 = (1.0+S11) / (1.0-S11) * Z0;
// error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead
QVERIFY(qFuzzyCompare((float)Z.get(1,1).real(), (float)Z11.real()));
QVERIFY(qFuzzyCompare((float)Z.get(1,1).imag(), (float)Z11.imag()));
}
}
void ParameterTests::S2Z_2P()
{
using namespace std::complex_literals;
std::complex<double> S11 = 0.0038 + 0.0248i;
std::complex<double> S12 = 0.9961 - 0.0250i;
std::complex<double> S21 = 0.9964 - 0.0254i;
std::complex<double> S22 = 0.0037 + 0.0249i;
auto S = Sparam(S11, S12, S21, S22);
// test for various characteristic impedances
for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) {
auto Z = Zparam(S, Z0);
// calculate expected Z values based on two-port formulas
auto deltaS = (1.0-S.get(1,1))*(1.0-S.get(2,2))-S.get(1,2)*S.get(2,1);
auto Z11 = ((1.0+S.get(1,1))*(1.0-S.get(2,2))+S.get(1,2)*S.get(2,1))/deltaS*Z0;
auto Z12 = 2.0*S.get(1,2)/deltaS*Z0;
auto Z21 = 2.0*S.get(2,1)/deltaS*Z0;
auto Z22 = ((1.0-S.get(1,1))*(1.0+S.get(2,2))+S.get(1,2)*S.get(2,1))/deltaS*Z0;
// error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead
QVERIFY(qFuzzyCompare((float)Z.get(1,1).real(), (float)Z11.real()));
QVERIFY(qFuzzyCompare((float)Z.get(1,1).imag(), (float)Z11.imag()));
QVERIFY(qFuzzyCompare((float)Z.get(1,2).real(), (float)Z12.real()));
QVERIFY(qFuzzyCompare((float)Z.get(1,2).imag(), (float)Z12.imag()));
QVERIFY(qFuzzyCompare((float)Z.get(2,1).real(), (float)Z21.real()));
QVERIFY(qFuzzyCompare((float)Z.get(2,1).imag(), (float)Z21.imag()));
QVERIFY(qFuzzyCompare((float)Z.get(2,2).real(), (float)Z22.real()));
QVERIFY(qFuzzyCompare((float)Z.get(2,2).imag(), (float)Z22.imag()));
}
}
void ParameterTests::Z2S_1P()
{
using namespace std::complex_literals;
std::complex<double> Z11 = 0.0038 + 0.0248i;
auto Z = Zparam(Z11);
// test for various characteristic impedances
for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) {
auto S = Sparam(Z, Z0);
// calculate expected Z values based on two-port formulas
auto S11 = (Z11/Z0-1.0) / (Z11/Z0+1.0);
// error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead
QVERIFY(qFuzzyCompare((float)S.get(1,1).real(), (float)S11.real()));
QVERIFY(qFuzzyCompare((float)S.get(1,1).imag(), (float)S11.imag()));
}
}
void ParameterTests::Z2S_2P()
{
using namespace std::complex_literals;
std::complex<double> Z11 = 0.0038 + 0.0248i;
std::complex<double> Z12 = 0.9961 - 0.0250i;
std::complex<double> Z21 = 0.9964 - 0.0254i;
std::complex<double> Z22 = 0.0037 + 0.0249i;
auto Z = Zparam(Z11, Z12, Z21, Z22);
// test for various characteristic impedances
for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) {
auto S = Sparam(Z, Z0);
// calculate expected Z values based on two-port formulas
auto delta = (Z.get(1,1)+Z0)*(Z.get(2,2)+Z0)-Z.get(1,2)*Z.get(2,1);
auto S11 = ((Z.get(1,1)-Z0)*(Z.get(2,2)+Z0)-Z.get(1,2)*Z.get(2,1))/delta;
auto S12 = 2.0*Z0*Z.get(1,2)/delta;
auto S21 = 2.0*Z0*Z.get(2,1)/delta;
auto S22 = ((Z.get(1,1)+Z0)*(Z.get(2,2)-Z0)-Z.get(1,2)*Z.get(2,1))/delta;
// error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead
QVERIFY(qFuzzyCompare((float)S.get(1,1).real(), (float)S11.real()));
QVERIFY(qFuzzyCompare((float)S.get(1,1).imag(), (float)S11.imag()));
QVERIFY(qFuzzyCompare((float)S.get(1,2).real(), (float)S12.real()));
QVERIFY(qFuzzyCompare((float)S.get(1,2).imag(), (float)S12.imag()));
QVERIFY(qFuzzyCompare((float)S.get(2,1).real(), (float)S21.real()));
QVERIFY(qFuzzyCompare((float)S.get(2,1).imag(), (float)S21.imag()));
QVERIFY(qFuzzyCompare((float)S.get(2,2).real(), (float)S22.real()));
QVERIFY(qFuzzyCompare((float)S.get(2,2).imag(), (float)S22.imag()));
}
}

View file

@ -12,6 +12,10 @@ public:
private slots:
void S2ABCD();
void ABCD2S();
void S2Z_1P();
void S2Z_2P();
void Z2S_1P();
void Z2S_2P();
};
#endif // PARAMETERTESTS_H