Through normalization and TRL calibration added
This commit is contained in:
parent
c6d9796a11
commit
49917c4b19
@ -42,31 +42,7 @@ void Calibration::addMeasurement(Calibration::Measurement type, Protocol::Datapo
|
||||
|
||||
bool Calibration::calculationPossible(Calibration::Type type)
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
switch(type) {
|
||||
case Type::Port1SOL:
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
break;
|
||||
case Type::Port2SOL:
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
break;
|
||||
case Type::FullSOLT:
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
requiredMeasurements.push_back(Measurement::Through);
|
||||
break;
|
||||
case Type::None:
|
||||
return false;
|
||||
}
|
||||
return SanityCheckSamples(requiredMeasurements);
|
||||
return SanityCheckSamples(Measurements(type, false));
|
||||
}
|
||||
|
||||
bool Calibration::constructErrorTerms(Calibration::Type type)
|
||||
@ -74,23 +50,19 @@ bool Calibration::constructErrorTerms(Calibration::Type type)
|
||||
if(!calculationPossible(type)) {
|
||||
return false;
|
||||
}
|
||||
if(minFreq < kit.minFreq() || maxFreq > kit.maxFreq()) {
|
||||
bool isTRL = type == Type::TRL;
|
||||
if(minFreq < kit.minFreq(isTRL) || maxFreq > kit.maxFreq(isTRL)) {
|
||||
// Calkit does not support complete calibration range
|
||||
QMessageBox::critical(nullptr, "Unable to perform calibration", "The calibration kit does not support the complete span. Please choose a different calibration kit or a narrower span.");
|
||||
return false;
|
||||
}
|
||||
switch(type) {
|
||||
case Type::Port1SOL:
|
||||
constructPort1SOL();
|
||||
break;
|
||||
case Type::Port2SOL:
|
||||
constructPort2SOL();
|
||||
break;
|
||||
case Type::FullSOLT:
|
||||
construct12TermPoints();
|
||||
break;
|
||||
case Type::None:
|
||||
break;
|
||||
case Type::Port1SOL: constructPort1SOL(); break;
|
||||
case Type::Port2SOL: constructPort2SOL(); break;
|
||||
case Type::FullSOLT: construct12TermPoints(); break;
|
||||
case Type::TransmissionNormalization: constructTransmissionNormalization(); break;
|
||||
case Type::TRL: constructTRL(); break;
|
||||
case Type::None: break;
|
||||
}
|
||||
this->type = type;
|
||||
return true;
|
||||
@ -104,17 +76,7 @@ void Calibration::resetErrorTerms()
|
||||
|
||||
void Calibration::construct12TermPoints()
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
requiredMeasurements.push_back(Measurement::Through);
|
||||
if(!SanityCheckSamples(requiredMeasurements)) {
|
||||
throw runtime_error("Missing/wrong calibration measurement");
|
||||
}
|
||||
std::vector<Measurement> requiredMeasurements = Measurements(Type::FullSOLT);
|
||||
requiredMeasurements.push_back(Measurement::Isolation);
|
||||
bool isolation_measured = SanityCheckSamples(requiredMeasurements);
|
||||
|
||||
@ -141,11 +103,11 @@ void Calibration::construct12TermPoints()
|
||||
auto S22_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S22, measurements[Measurement::Through].datapoints[i].imag_S22);
|
||||
auto S12_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12);
|
||||
|
||||
auto actual = kit.toReflection(p.frequency);
|
||||
auto actual = kit.toSOLT(p.frequency);
|
||||
// Forward calibration
|
||||
computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
|
||||
p.fe30 = S21_isolation;
|
||||
// See page 17 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
// See page 18 of https://www.rfmentor.com/sites/default/files/NA_Error_Models_and_Cal_Methods.pdf
|
||||
// Formulas for S11M and S21M solved for e22 and e10e32
|
||||
auto deltaS = actual.ThroughS11*actual.ThroughS22 - actual.ThroughS21 * actual.ThroughS12;
|
||||
p.fe22 = ((S11_through - p.fe00)*(1.0 - p.fe11 * actual.ThroughS11)-actual.ThroughS11*p.fe10e01)
|
||||
@ -163,15 +125,6 @@ void Calibration::construct12TermPoints()
|
||||
|
||||
void Calibration::constructPort1SOL()
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
requiredMeasurements.push_back(Measurement::Port1Open);
|
||||
requiredMeasurements.push_back(Measurement::Port1Short);
|
||||
requiredMeasurements.push_back(Measurement::Port1Load);
|
||||
if(!SanityCheckSamples(requiredMeasurements)) {
|
||||
throw runtime_error("Missing/wrong calibration measurement");
|
||||
}
|
||||
|
||||
// If we get here the calibration measurements are all okay
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Port1Open].datapoints.size();i++) {
|
||||
Point p;
|
||||
@ -181,8 +134,8 @@ void Calibration::constructPort1SOL()
|
||||
auto S11_short = complex<double>(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
|
||||
auto S11_load = complex<double>(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
|
||||
// OSL port1
|
||||
auto actual = kit.toReflection(p.frequency);
|
||||
// See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
auto actual = kit.toSOLT(p.frequency);
|
||||
// See page 13 of https://www.rfmentor.com/sites/default/files/NA_Error_Models_and_Cal_Methods.pdf
|
||||
computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
|
||||
// All other calibration coefficients to ideal values
|
||||
p.fe30 = 0.0;
|
||||
@ -200,15 +153,6 @@ void Calibration::constructPort1SOL()
|
||||
|
||||
void Calibration::constructPort2SOL()
|
||||
{
|
||||
std::vector<Measurement> requiredMeasurements;
|
||||
requiredMeasurements.push_back(Measurement::Port2Open);
|
||||
requiredMeasurements.push_back(Measurement::Port2Short);
|
||||
requiredMeasurements.push_back(Measurement::Port2Load);
|
||||
if(!SanityCheckSamples(requiredMeasurements)) {
|
||||
throw runtime_error("Missing/wrong calibration measurement");
|
||||
}
|
||||
|
||||
// If we get here the calibration measurements are all okay
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Port2Open].datapoints.size();i++) {
|
||||
Point p;
|
||||
@ -218,8 +162,8 @@ void Calibration::constructPort2SOL()
|
||||
auto S22_short = complex<double>(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
|
||||
auto S22_load = complex<double>(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
|
||||
// OSL port2
|
||||
auto actual = kit.toReflection(p.frequency);
|
||||
// See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
auto actual = kit.toSOLT(p.frequency);
|
||||
// See page 19 of https://www.rfmentor.com/sites/default/files/NA_Error_Models_and_Cal_Methods.pdf
|
||||
computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
|
||||
// All other calibration coefficients to ideal values
|
||||
p.fe30 = 0.0;
|
||||
@ -235,6 +179,187 @@ void Calibration::constructPort2SOL()
|
||||
}
|
||||
}
|
||||
|
||||
void Calibration::constructTransmissionNormalization()
|
||||
{
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Through].datapoints.size();i++) {
|
||||
Point p;
|
||||
p.frequency = measurements[Measurement::Through].datapoints[i].frequency;
|
||||
// extract required complex reflection/transmission factors from datapoints
|
||||
auto S21_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S21, measurements[Measurement::Through].datapoints[i].imag_S21);
|
||||
auto S12_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12);
|
||||
auto actual = kit.toSOLT(p.frequency);
|
||||
p.fe10e32 = S21_through / actual.ThroughS21;
|
||||
p.re23e01 = S12_through / actual.ThroughS12;
|
||||
// All other calibration coefficients to ideal values
|
||||
p.fe30 = 0.0;
|
||||
p.fe22 = 0.0;
|
||||
p.fe00 = 0.0;
|
||||
p.fe11 = 0.0;
|
||||
p.fe10e01 = 1.0;
|
||||
p.re03 = 0.0;
|
||||
p.re11 = 0.0;
|
||||
p.re33 = 0.0;
|
||||
p.re22 = 0.0;
|
||||
p.re23e32 = 1.0;
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class Tparam {
|
||||
public:
|
||||
Tparam(){};
|
||||
Tparam(T t11, T t12, T t21, T t22)
|
||||
: t11(t11), t12(t12), t21(t21), t22(t22){};
|
||||
void fromSparam(T S11, T S21, T S12, T S22) {
|
||||
t11 = -(S11*S22 - S12*S21) / S21;
|
||||
t12 = S11 / S21;
|
||||
t21 = -S22 / S21;
|
||||
t22 = 1.0 / S21;
|
||||
}
|
||||
void toSparam(T &S11, T &S21, T &S12, T &S22) {
|
||||
S11 = t12 / t22;
|
||||
S21 = T(1) / t22;
|
||||
S12 = (t11*t22 - t12*t21) / t22;
|
||||
S22 = -t21 / t22;
|
||||
}
|
||||
Tparam inverse() {
|
||||
Tparam i;
|
||||
T det = t11*t22 - t12*t21;
|
||||
i.t11 = t22 / det;
|
||||
i.t12 = -t12 / det;
|
||||
i.t21 = -t21 / det;
|
||||
i.t22 = t11 / det;
|
||||
return i;
|
||||
}
|
||||
Tparam operator*(const Tparam &r) {
|
||||
Tparam p;
|
||||
p.t11 = t11*r.t11 + t12*r.t21;
|
||||
p.t12 = t11*r.t12 + t12*r.t22;
|
||||
p.t21 = t21*r.t11 + t22*r.t21;
|
||||
p.t22 = t21*r.t12 + t22*r.t22;
|
||||
return p;
|
||||
}
|
||||
Tparam operator*(const T &r) {
|
||||
Tparam p;
|
||||
p.t11 = t11 * r;
|
||||
p.t12 = t12 * r;
|
||||
p.t21 = t21 * r;
|
||||
p.t22 = t22 * r;
|
||||
return p;
|
||||
}
|
||||
T t11, t12, t21, t22;
|
||||
};
|
||||
|
||||
template<typename T> void solveQuadratic(T a, T b, T c, T &result1, T &result2)
|
||||
{
|
||||
T root = sqrt(b * b - T(4) * a * c);
|
||||
result1 = (-b + root) / (T(2) * a);
|
||||
result2 = (-b - root) / (T(2) * a);
|
||||
}
|
||||
|
||||
void Calibration::constructTRL()
|
||||
{
|
||||
points.clear();
|
||||
for(unsigned int i = 0;i<measurements[Measurement::Through].datapoints.size();i++) {
|
||||
Point p;
|
||||
p.frequency = measurements[Measurement::Through].datapoints[i].frequency;
|
||||
|
||||
// grab raw measurements
|
||||
auto S11_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S11, measurements[Measurement::Through].datapoints[i].imag_S11);
|
||||
auto S21_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S21, measurements[Measurement::Through].datapoints[i].imag_S21);
|
||||
auto S22_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S22, measurements[Measurement::Through].datapoints[i].imag_S22);
|
||||
auto S12_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12);
|
||||
auto S11_line = complex<double>(measurements[Measurement::Line].datapoints[i].real_S11, measurements[Measurement::Line].datapoints[i].imag_S11);
|
||||
auto S21_line = complex<double>(measurements[Measurement::Line].datapoints[i].real_S21, measurements[Measurement::Line].datapoints[i].imag_S21);
|
||||
auto S22_line = complex<double>(measurements[Measurement::Line].datapoints[i].real_S22, measurements[Measurement::Line].datapoints[i].imag_S22);
|
||||
auto S12_line = complex<double>(measurements[Measurement::Line].datapoints[i].real_S12, measurements[Measurement::Line].datapoints[i].imag_S12);
|
||||
auto trl = kit.toTRL(p.frequency);
|
||||
complex<double> S11_reflection, S22_reflection;
|
||||
if(trl.reflectionIsNegative) {
|
||||
// used short
|
||||
S11_reflection = complex<double>(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
|
||||
S22_reflection = complex<double>(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
|
||||
} else {
|
||||
// used open
|
||||
S11_reflection = complex<double>(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
|
||||
S22_reflection = complex<double>(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
|
||||
}
|
||||
// calculate TRL calibration
|
||||
// variable names and formulas according to http://emlab.uiuc.edu/ece451/notes/new_TRL.pdf
|
||||
// page 19
|
||||
auto R_T = Tparam<complex<double>>();
|
||||
auto R_D = Tparam<complex<double>>();
|
||||
R_T.fromSparam(S11_through, S21_through, S12_through, S22_through);
|
||||
R_D.fromSparam(S11_line, S21_line, S12_line, S22_line);
|
||||
auto T = R_D*R_T.inverse();
|
||||
complex<double> a_over_c, b;
|
||||
// page 21-22
|
||||
solveQuadratic(T.t21, T.t22 - T.t11, -T.t12, b, a_over_c);
|
||||
// ensure correct root selection
|
||||
// page 23
|
||||
if(abs(b) >= abs(a_over_c)) {
|
||||
swap(b, a_over_c);
|
||||
}
|
||||
// page 24
|
||||
auto g = R_T.t22;
|
||||
auto d = R_T.t11 / g;
|
||||
auto e = R_T.t12 / g;
|
||||
auto f = R_T.t21 / g;
|
||||
|
||||
// page 25
|
||||
auto r22_rho22 = g * (1.0 - e / a_over_c) / (1.0 - b / a_over_c);
|
||||
auto gamma = (f - d / a_over_c) / (1.0 - e / a_over_c);
|
||||
auto beta_over_alpha = (e - b) / (d - b * f);
|
||||
// page 26
|
||||
auto alpha_a = (d - b * f) / (1.0 - e / a_over_c);
|
||||
auto w1 = S11_reflection;
|
||||
auto w2 = S22_reflection;
|
||||
// page 28
|
||||
auto a = sqrt((w1 - b) / (w2 + gamma) * (1.0 + w2 * beta_over_alpha) / (1.0 - w1 / a_over_c) * alpha_a);
|
||||
// page 29, check sign of a
|
||||
auto reflection = (w1 - b) / (a * (1.0 - w1 / a_over_c));
|
||||
if((reflection.real() > 0 && trl.reflectionIsNegative) || (reflection.real() < 0 && !trl.reflectionIsNegative)) {
|
||||
// wrong sign for a
|
||||
a = -a;
|
||||
}
|
||||
// Revert back from error boxes with T parameters to S paramaters,
|
||||
// page 17 + formulas for calculating S parameters from T parameters.
|
||||
// Forward coefficients, normalize for S21 = 1.0 -> r22 = 1.0
|
||||
auto r22 = complex<double>(1.0);
|
||||
auto rho22 = r22_rho22 / r22;
|
||||
auto alpha = alpha_a / a;
|
||||
auto beta = beta_over_alpha * alpha;
|
||||
auto c = a / a_over_c;
|
||||
auto Box_A = Tparam<complex<double>>(r22 * a, r22 * b, r22 * c, r22);
|
||||
auto Box_B = Tparam<complex<double>>(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22);
|
||||
// e00 is S11_A, S11 = T12/T22 = r22*a/r22
|
||||
complex<double> dummy1, dummy2;
|
||||
Box_A.toSparam(p.fe00, dummy1, p.fe10e01, p.fe11);
|
||||
Box_B.toSparam(p.fe22, p.fe10e32, dummy1, dummy2);
|
||||
// no isolation measurement available
|
||||
p.fe30 = 0.0;
|
||||
|
||||
// Reverse coefficients, normalize for S12 = 1.0
|
||||
// => det(T)/T22 = 1.0
|
||||
// => (rho22*alpa*rho22 - rho22*beta*rho*gamma)/rho22 = 1.0
|
||||
// => rho22*alpha - rho22*beta*gamma = 1.0
|
||||
// => rho22 = 1.0/(alpha - beta * gamma)
|
||||
rho22 = 1.0/(alpha - beta * gamma);
|
||||
r22 = r22_rho22 / rho22;
|
||||
|
||||
Box_A = Tparam<complex<double>>(r22 * a, r22 * b, r22 * c, r22);
|
||||
Box_B = Tparam<complex<double>>(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22);
|
||||
Box_A.toSparam(dummy1, dummy2, p.re23e01, p.re11);
|
||||
Box_B.toSparam(p.re22, p.re23e32, dummy1, p.re33);
|
||||
// no isolation measurement available
|
||||
p.re03 = 0.0;
|
||||
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Calibration::correctMeasurement(Protocol::Datapoint &d)
|
||||
{
|
||||
if(type == Type::None) {
|
||||
@ -250,15 +375,17 @@ void Calibration::correctMeasurement(Protocol::Datapoint &d)
|
||||
// find correct entry
|
||||
auto p = getCalibrationPoint(d);
|
||||
|
||||
// equations from page 20 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
|
||||
complex<double> S11, S12, S21, S22;
|
||||
|
||||
// equations from page 19 of https://www.rfmentor.com/sites/default/files/NA_Error_Models_and_Cal_Methods.pdf
|
||||
auto denom = (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11) * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
|
||||
- (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01 * p.fe22 * p.re11;
|
||||
auto S11 = ((S11m - p.fe00) / p.fe10e01 * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
|
||||
S11 = ((S11m - p.fe00) / p.fe10e01 * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
|
||||
- p.fe22 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
|
||||
auto S21 = ((S21m - p.fe30) / p.fe10e32 * (1.0 + (S22m - p.re33) / p.re23e32 * (p.re22 - p.fe22))) / denom;
|
||||
auto S22 = ((S22m - p.re33) / p.re23e32 * (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11)
|
||||
S21 = ((S21m - p.fe30) / p.fe10e32 * (1.0 + (S22m - p.re33) / p.re23e32 * (p.re22 - p.fe22))) / denom;
|
||||
S22 = ((S22m - p.re33) / p.re23e32 * (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11)
|
||||
- p.re11 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
|
||||
auto S12 = ((S12m - p.re03) / p.re23e01 * (1.0 + (S11m - p.fe00) / p.fe10e01 * (p.fe11 - p.re11))) / denom;
|
||||
S12 = ((S12m - p.re03) / p.re23e01 * (1.0 + (S11m - p.fe00) / p.fe10e01 * (p.fe11 - p.re11))) / denom;
|
||||
|
||||
d.real_S11 = S11.real();
|
||||
d.imag_S11 = S11.imag();
|
||||
@ -319,6 +446,8 @@ QString Calibration::MeasurementToString(Calibration::Measurement m)
|
||||
return "Through";
|
||||
case Measurement::Isolation:
|
||||
return "Isolation";
|
||||
case Measurement::Line:
|
||||
return "Line";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
@ -330,22 +459,32 @@ QString Calibration::TypeToString(Calibration::Type t)
|
||||
case Type::Port1SOL: return "Port 1"; break;
|
||||
case Type::Port2SOL: return "Port 2"; break;
|
||||
case Type::FullSOLT: return "SOLT"; break;
|
||||
case Type::TransmissionNormalization: return "Normalize"; break;
|
||||
case Type::TRL: return "TRL"; break;
|
||||
default: return "None"; break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Calibration::Type> Calibration::Types()
|
||||
{
|
||||
const std::vector<Calibration::Type> ret = {Type::Port1SOL, Type::Port2SOL, Type::FullSOLT};
|
||||
const std::vector<Calibration::Type> ret = {Type::Port1SOL, Type::Port2SOL, Type::FullSOLT, Type::TransmissionNormalization, Type::TRL};
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<Calibration::Measurement> Calibration::Measurements(Calibration::Type type)
|
||||
const std::vector<Calibration::Measurement> Calibration::Measurements(Calibration::Type type, bool optional_included)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::FullSOLT:
|
||||
case Type::None:
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load, Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load, Measurement::Through, Measurement::Isolation};
|
||||
// all possible measurements
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load,
|
||||
Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load,
|
||||
Measurement::Through, Measurement::Isolation, Measurement::Line};
|
||||
case Type::FullSOLT:
|
||||
if(optional_included) {
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load, Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load, Measurement::Through, Measurement::Isolation};
|
||||
} else {
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load, Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load, Measurement::Through};
|
||||
}
|
||||
break;
|
||||
case Type::Port1SOL:
|
||||
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load};
|
||||
@ -353,6 +492,16 @@ const std::vector<Calibration::Measurement> Calibration::Measurements(Calibratio
|
||||
case Type::Port2SOL:
|
||||
return {Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load};
|
||||
break;
|
||||
case Type::TransmissionNormalization:
|
||||
return {Measurement::Through};
|
||||
break;
|
||||
case Type::TRL:
|
||||
if(kit.isTRLReflectionShort()) {
|
||||
return {Measurement::Through, Measurement::Port1Short, Measurement::Port2Short, Measurement::Line};
|
||||
} else {
|
||||
return {Measurement::Through, Measurement::Port1Open, Measurement::Port2Open, Measurement::Line};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
break;
|
||||
@ -394,6 +543,11 @@ Calibration::MeasurementInfo Calibration::getMeasurementInfo(Calibration::Measur
|
||||
case Measurement::Isolation:
|
||||
info.name = "Isolation";
|
||||
info.prerequisites = "Both ports terminated into 50 ohm";
|
||||
break;
|
||||
case Measurement::Line:
|
||||
info.name = "Line";
|
||||
info.prerequisites = "Port 1 connected to port 2 via line standard";
|
||||
break;
|
||||
}
|
||||
info.points = measurements[m].datapoints.size();
|
||||
if(info.points > 0) {
|
||||
@ -524,7 +678,7 @@ istream& operator >>(istream &in, Calibration &c)
|
||||
{
|
||||
std::string line;
|
||||
while(getline(in, line)) {
|
||||
for(auto m : Calibration::Measurements()) {
|
||||
for(auto m : c.Measurements()) {
|
||||
if(Calibration::MeasurementToString(m) == QString::fromStdString(line)) {
|
||||
// this is the correct measurement
|
||||
c.clearMeasurement(m);
|
||||
@ -561,7 +715,7 @@ istream& operator >>(istream &in, Calibration &c)
|
||||
return in;
|
||||
}
|
||||
|
||||
bool Calibration::SanityCheckSamples(std::vector<Calibration::Measurement> &requiredMeasurements)
|
||||
bool Calibration::SanityCheckSamples(const std::vector<Calibration::Measurement> &requiredMeasurements)
|
||||
{
|
||||
// sanity check measurements, all need to be of the same size with the same frequencies (except for isolation which may be empty)
|
||||
vector<uint64_t> freqs;
|
||||
|
@ -26,6 +26,7 @@ public:
|
||||
Port2Load,
|
||||
Isolation,
|
||||
Through,
|
||||
Line,
|
||||
};
|
||||
void clearMeasurements();
|
||||
void clearMeasurement(Measurement type);
|
||||
@ -35,6 +36,8 @@ public:
|
||||
Port1SOL,
|
||||
Port2SOL,
|
||||
FullSOLT,
|
||||
TransmissionNormalization,
|
||||
TRL,
|
||||
None,
|
||||
};
|
||||
|
||||
@ -67,7 +70,7 @@ public:
|
||||
};
|
||||
|
||||
static const std::vector<Type> Types();
|
||||
static const std::vector<Measurement> Measurements(Type type = Type::None);
|
||||
const std::vector<Measurement> Measurements(Type type = Type::None, bool optional_included = true);
|
||||
MeasurementInfo getMeasurementInfo(Measurement m);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Calibration& c);
|
||||
@ -89,7 +92,9 @@ private:
|
||||
void construct12TermPoints();
|
||||
void constructPort1SOL();
|
||||
void constructPort2SOL();
|
||||
bool SanityCheckSamples(std::vector<Measurement> &requiredMeasurements);
|
||||
void constructTransmissionNormalization();
|
||||
void constructTRL();
|
||||
bool SanityCheckSamples(const std::vector<Measurement> &requiredMeasurements);
|
||||
class Point
|
||||
{
|
||||
public:
|
||||
|
@ -64,7 +64,9 @@ void CalibrationTraceDialog::on_bOpen_clicked()
|
||||
{
|
||||
cal->openFromFile();
|
||||
UpdateApplyButton();
|
||||
emit applyCalibration(cal->getType());
|
||||
if(cal->getType() != Calibration::Type::None) {
|
||||
emit applyCalibration(cal->getType());
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationTraceDialog::on_bSave_clicked()
|
||||
|
@ -51,6 +51,12 @@ Calkit::Calkit()
|
||||
load_Sparam = 0;
|
||||
through_Sparam1 = 0;
|
||||
through_Sparam2 = 1;
|
||||
|
||||
TRL_through_Z0 = 50.0;
|
||||
TRL_reflection_short = true;
|
||||
TRL_line_delay = 74;
|
||||
TRL_line_maxfreq = 6000000000;
|
||||
TRL_line_minfreq = 751000000;
|
||||
}
|
||||
|
||||
void Calkit::toFile(std::string filename)
|
||||
@ -75,6 +81,11 @@ void Calkit::toFile(std::string filename)
|
||||
if(through_measurements) {
|
||||
file << through_file << "\n" << through_Sparam1 << "\n" << through_Sparam2 << "\n";
|
||||
}
|
||||
file << TRL_through_Z0 << "\n";
|
||||
file << TRL_reflection_short << "\n";
|
||||
file << TRL_line_delay << "\n";
|
||||
file << TRL_line_minfreq << "\n";
|
||||
file << TRL_line_maxfreq << "\n";
|
||||
file.close();
|
||||
}
|
||||
|
||||
@ -125,6 +136,11 @@ Calkit Calkit::fromFile(std::string filename)
|
||||
file >> c.through_Sparam1;
|
||||
file >> c.through_Sparam2;
|
||||
}
|
||||
file >> c.TRL_through_Z0;
|
||||
file >> c.TRL_reflection_short;
|
||||
file >> c.TRL_line_delay;
|
||||
file >> c.TRL_line_minfreq;
|
||||
file >> c.TRL_line_maxfreq;
|
||||
file.close();
|
||||
return c;
|
||||
}
|
||||
@ -135,10 +151,10 @@ void Calkit::edit()
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
Calkit::Reflection Calkit::toReflection(double frequency)
|
||||
Calkit::SOLT Calkit::toSOLT(double frequency)
|
||||
{
|
||||
fillTouchstoneCache();
|
||||
Reflection ref;
|
||||
SOLT ref;
|
||||
if(load_measurements) {
|
||||
ref.Load = ts_load->interpolate(frequency).S[0];
|
||||
} else {
|
||||
@ -204,40 +220,66 @@ Calkit::Reflection Calkit::toReflection(double frequency)
|
||||
return ref;
|
||||
}
|
||||
|
||||
double Calkit::minFreq()
|
||||
Calkit::TRL Calkit::toTRL(double)
|
||||
{
|
||||
fillTouchstoneCache();
|
||||
double min = std::numeric_limits<double>::min();
|
||||
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
|
||||
// find the highest minimum frequency in all measurement files
|
||||
for(auto ts : ts_list) {
|
||||
if(!ts) {
|
||||
// this calibration standard is defined by coefficients, no minimum frequency
|
||||
continue;
|
||||
}
|
||||
if(ts->minFreq() > min) {
|
||||
min = ts->minFreq();
|
||||
}
|
||||
}
|
||||
return min;
|
||||
TRL trl;
|
||||
// reflection coefficent sign depends on whether an open or short is used
|
||||
trl.reflectionIsNegative = TRL_reflection_short;
|
||||
// assume ideal through for now
|
||||
trl.ThroughS11 = 0.0;
|
||||
trl.ThroughS12 = 1.0;
|
||||
trl.ThroughS21 = 1.0;
|
||||
trl.ThroughS22 = 0.0;
|
||||
return trl;
|
||||
}
|
||||
|
||||
double Calkit::maxFreq()
|
||||
double Calkit::minFreq(bool TRL)
|
||||
{
|
||||
fillTouchstoneCache();
|
||||
double max = std::numeric_limits<double>::max();
|
||||
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
|
||||
// find the highest minimum frequency in all measurement files
|
||||
for(auto ts : ts_list) {
|
||||
if(!ts) {
|
||||
// this calibration standard is defined by coefficients, no minimum frequency
|
||||
continue;
|
||||
}
|
||||
if(ts->maxFreq() < max) {
|
||||
max = ts->maxFreq();
|
||||
if(TRL) {
|
||||
return TRL_line_minfreq;
|
||||
} else {
|
||||
fillTouchstoneCache();
|
||||
double min = std::numeric_limits<double>::min();
|
||||
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
|
||||
// find the highest minimum frequency in all measurement files
|
||||
for(auto ts : ts_list) {
|
||||
if(!ts) {
|
||||
// this calibration standard is defined by coefficients, no minimum frequency
|
||||
continue;
|
||||
}
|
||||
if(ts->minFreq() > min) {
|
||||
min = ts->minFreq();
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
double Calkit::maxFreq(bool TRL)
|
||||
{
|
||||
if(TRL) {
|
||||
return TRL_line_maxfreq;
|
||||
} else {
|
||||
fillTouchstoneCache();
|
||||
double max = std::numeric_limits<double>::max();
|
||||
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
|
||||
// find the highest minimum frequency in all measurement files
|
||||
for(auto ts : ts_list) {
|
||||
if(!ts) {
|
||||
// this calibration standard is defined by coefficients, no minimum frequency
|
||||
continue;
|
||||
}
|
||||
if(ts->maxFreq() < max) {
|
||||
max = ts->maxFreq();
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
bool Calkit::isTRLReflectionShort() const
|
||||
{
|
||||
return TRL_reflection_short;
|
||||
}
|
||||
|
||||
void Calkit::clearTouchstoneCache()
|
||||
|
@ -11,7 +11,7 @@ class Calkit
|
||||
public:
|
||||
Calkit();
|
||||
|
||||
class Reflection {
|
||||
class SOLT {
|
||||
public:
|
||||
std::complex<double> Open;
|
||||
std::complex<double> Short;
|
||||
@ -19,13 +19,23 @@ public:
|
||||
std::complex<double> ThroughS11, ThroughS12, ThroughS21, ThroughS22;
|
||||
};
|
||||
|
||||
class TRL {
|
||||
public:
|
||||
bool reflectionIsNegative;
|
||||
std::complex<double> ThroughS11, ThroughS12, ThroughS21, ThroughS22;
|
||||
};
|
||||
|
||||
void toFile(std::string filename);
|
||||
static Calkit fromFile(std::string filename);
|
||||
void edit();
|
||||
Reflection toReflection(double frequency);
|
||||
double minFreq();
|
||||
double maxFreq();
|
||||
SOLT toSOLT(double frequency);
|
||||
TRL toTRL(double frequency);
|
||||
double minFreq(bool TRL = false);
|
||||
double maxFreq(bool TRL = false);
|
||||
bool isTRLReflectionShort() const;
|
||||
|
||||
private:
|
||||
// SOLT standard definitions
|
||||
double open_Z0, open_delay, open_loss, open_C0, open_C1, open_C2, open_C3;
|
||||
double short_Z0, short_delay, short_loss, short_L0, short_L1, short_L2, short_L3;
|
||||
double load_Z0;
|
||||
@ -37,6 +47,13 @@ private:
|
||||
bool load_measurements;
|
||||
bool through_measurements;
|
||||
|
||||
// TRL standard definitions
|
||||
double TRL_through_Z0;
|
||||
bool TRL_reflection_short;
|
||||
double TRL_line_delay;
|
||||
double TRL_line_minfreq;
|
||||
double TRL_line_maxfreq;
|
||||
|
||||
std::string open_file, short_file, load_file, through_file;
|
||||
int open_Sparam, short_Sparam, load_Sparam, through_Sparam1, through_Sparam2;
|
||||
|
||||
|
@ -38,10 +38,30 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
|
||||
ui->load_touchstone->setPorts(1);
|
||||
ui->through_touchstone->setPorts(2);
|
||||
|
||||
ui->TRL_line_max->setUnit("Hz");
|
||||
ui->TRL_line_max->setPrecision(4);
|
||||
ui->TRL_line_max->setPrefixes(" kMG");
|
||||
ui->TRL_line_min->setUnit("Hz");
|
||||
ui->TRL_line_min->setPrecision(4);
|
||||
ui->TRL_line_min->setPrefixes(" kMG");
|
||||
|
||||
editKit.clearTouchstoneCache();
|
||||
ownKit = editKit;
|
||||
updateEntries();
|
||||
|
||||
connect(ui->TRL_line_min, &SIUnitEdit::valueChanged, [=](double newval){
|
||||
ownKit.TRL_line_minfreq = newval;
|
||||
updateEntries();
|
||||
});
|
||||
connect(ui->TRL_line_max, &SIUnitEdit::valueChanged, [=](double newval){
|
||||
ownKit.TRL_line_maxfreq = newval;
|
||||
updateEntries();
|
||||
});
|
||||
connect(ui->TRL_line_delay, &QLineEdit::editingFinished, [=](){
|
||||
ownKit.TRL_line_delay = ui->TRL_line_delay->text().toDouble();
|
||||
updateEntries();
|
||||
});
|
||||
|
||||
auto UpdateStatus = [=]() {
|
||||
bool ok = true;
|
||||
if(ui->open_measurement->isChecked() && !ui->open_touchstone->getStatus()) {
|
||||
@ -110,7 +130,6 @@ CalkitDialog::~CalkitDialog()
|
||||
|
||||
void CalkitDialog::parseEntries()
|
||||
{
|
||||
|
||||
// type
|
||||
ownKit.open_measurements = ui->open_measurement->isChecked();
|
||||
ownKit.short_measurements = ui->short_measurement->isChecked();
|
||||
@ -151,6 +170,13 @@ void CalkitDialog::parseEntries()
|
||||
ownKit.load_Sparam = ui->load_touchstone->getPorts()[0];
|
||||
ownKit.through_Sparam1 = ui->through_touchstone->getPorts()[0];
|
||||
ownKit.through_Sparam2 = ui->through_touchstone->getPorts()[1];
|
||||
|
||||
// TRL
|
||||
ownKit.TRL_through_Z0 = ui->TRL_through_Z0->text().toDouble();
|
||||
ownKit.TRL_reflection_short = ui->TRL_R_short->isChecked();
|
||||
ownKit.TRL_line_delay = ui->TRL_line_delay->text().toDouble();
|
||||
ownKit.TRL_line_minfreq = ui->TRL_line_min->value();
|
||||
ownKit.TRL_line_maxfreq = ui->TRL_line_max->value();
|
||||
}
|
||||
|
||||
void CalkitDialog::updateEntries()
|
||||
@ -216,4 +242,25 @@ void CalkitDialog::updateEntries()
|
||||
} else {
|
||||
ui->through_coefficients->click();
|
||||
}
|
||||
|
||||
// TRL
|
||||
ui->TRL_through_Z0->setText(QString::number(ownKit.TRL_through_Z0));
|
||||
if(ownKit.TRL_reflection_short) {
|
||||
ui->TRL_R_short->setChecked(true);
|
||||
} else {
|
||||
ui->TRL_R_open->setChecked(true);
|
||||
}
|
||||
ui->TRL_line_delay->setText(QString::number(ownKit.TRL_line_delay));
|
||||
ui->TRL_line_min->setValueQuiet(ownKit.TRL_line_minfreq);
|
||||
ui->TRL_line_max->setValueQuiet(ownKit.TRL_line_maxfreq);
|
||||
// Check if line length is appropriate for frequencies
|
||||
auto minDelay = 20.0/(ownKit.TRL_line_minfreq * 360.0) * 1e12;
|
||||
auto maxDelay = 160.0/(ownKit.TRL_line_maxfreq * 360.0) * 1e12;
|
||||
if(ownKit.TRL_line_delay < minDelay) {
|
||||
ui->TRL_line_warning->setText("Line too short, minimum required delay is "+QString::number(minDelay, 'g', 3) + "ps");
|
||||
} else if(ownKit.TRL_line_delay > maxDelay) {
|
||||
ui->TRL_line_warning->setText("Line too long, maximum allowed delay is "+QString::number(maxDelay, 'g', 3) + "ps");
|
||||
} else {
|
||||
ui->TRL_line_warning->clear();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -53,7 +53,6 @@ QVariant MeasurementModel::data(const QModelIndex &index, int role) const
|
||||
case ColIndexDescription: return 500; break;
|
||||
case ColIndexData: return 300; break;
|
||||
case ColIndexDate: return 300; break;
|
||||
case ColIndexStatusSymbol: return 150; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
}
|
||||
@ -69,7 +68,6 @@ QVariant MeasurementModel::headerData(int section, Qt::Orientation orientation,
|
||||
case ColIndexDescription: return "Prerequisites"; break;
|
||||
case ColIndexData: return "Statistics"; break;
|
||||
case ColIndexDate: return "Timestamp"; break;
|
||||
case ColIndexStatusSymbol: return "Status"; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
} else {
|
||||
|
@ -26,7 +26,6 @@ private:
|
||||
ColIndexDescription,
|
||||
ColIndexData,
|
||||
ColIndexDate,
|
||||
ColIndexStatusSymbol,
|
||||
ColIndexLast
|
||||
};
|
||||
Calibration *cal;
|
||||
|
@ -702,10 +702,11 @@ void VNA::ApplyCalibration(Calibration::Type type)
|
||||
{
|
||||
if(cal.calculationPossible(type)) {
|
||||
try {
|
||||
cal.constructErrorTerms(type);
|
||||
calValid = true;
|
||||
average.reset();
|
||||
emit CalibrationApplied(type);
|
||||
if(cal.constructErrorTerms(type)) {
|
||||
calValid = true;
|
||||
average.reset();
|
||||
emit CalibrationApplied(type);
|
||||
}
|
||||
} catch (runtime_error e) {
|
||||
QMessageBox::critical(this, "Calibration failure", e.what());
|
||||
DisableCalibration(true);
|
||||
|
Loading…
Reference in New Issue
Block a user