diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1aa3f..8b5e845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## v1.2.1 + +Mostly bugfixes along with the occasional new feature. + +- Calibration: + - File format changed to json + - Multiple measurements can be taken/deleted at the same time + - Calibration kit allows separate male/female standards + - configurable Z0 for short/open +- SCPI commands: + - load/save calibration files + - export to touchstone file format directly + - additional command for reading trace data + - fix typo in documentation +- UI improvements: + - Additional Y-axis options: Reactance/Real/Imaginary + - Graphs look a bit nicer + - Configurable line width for graphs + - finally added an application logo +- General bugfixes, among others: + - PLL divider calculation fixed for certain frequencies + - Improved USB buffer handling + - Better error handling when opening invalid files + - Various bugs when adding/deleting markers + - graph autoscaling with invisible traces + ## v1.2.0 - Additional SCPI commands diff --git a/Documentation/UserManual/ProgrammingGuide.pdf b/Documentation/UserManual/ProgrammingGuide.pdf index c9ef6c0..c69a0fe 100644 Binary files a/Documentation/UserManual/ProgrammingGuide.pdf and b/Documentation/UserManual/ProgrammingGuide.pdf differ diff --git a/Documentation/UserManual/ProgrammingGuide.tex b/Documentation/UserManual/ProgrammingGuide.tex index 09e5808..c7571db 100644 --- a/Documentation/UserManual/ProgrammingGuide.tex +++ b/Documentation/UserManual/ProgrammingGuide.tex @@ -241,11 +241,11 @@ This section contains general device commands, available regardless of the curre \subsubsection{DEVice:MODE} \event{Switches the device to the specified mode}{DEVice:MODE }{:\\ \hspace{1cm} VNA: set to vector analyzer\\ \hspace{1cm} GEN: set to signal generator\\ \hspace{1cm} SA: set to spectrum analyzer} \begin{example} -:MODE VNA +:DEV:MODE VNA \end{example} \query{Queries the currently active mode}{DEVice:MODE?}{None}{:\\ \hspace{1cm} VNA: set to vector analyzer\\ \hspace{1cm} GEN: set to signal generator\\ \hspace{1cm} SA: set to spectrum analyzer} \begin{example} -:MODE? +:DEV:MODE? VNA \end{example} diff --git a/FPGA/VNA/top.vhd b/FPGA/VNA/top.vhd index 391a933..ebd5e22 100644 --- a/FPGA/VNA/top.vhd +++ b/FPGA/VNA/top.vhd @@ -311,7 +311,7 @@ architecture Behavioral of top is ); END COMPONENT; - signal clk160 : std_logic; + signal clk_pll : std_logic; signal clk_locked : std_logic; signal inv_clk_locked : std_logic; signal int_reset : std_logic; @@ -453,7 +453,7 @@ begin -- Clock in ports CLK_IN1 => CLK, -- Clock out ports - CLK_OUT1 => clk160, + CLK_OUT1 => clk_pll, -- Status and control signals RESET => RESET, LOCKED => clk_locked @@ -464,7 +464,7 @@ begin Inst_ResetDelay: ResetDelay GENERIC MAP(CLK_DELAY => 100) PORT MAP( - CLK => clk160, + CLK => clk_pll, IN_RESET => inv_clk_locked, OUT_RESET => int_reset ); @@ -472,42 +472,42 @@ begin Sync_AUX1 : Synchronizer GENERIC MAP(stages => 2) PORT MAP( - CLK => clk160, + CLK => clk_pll, SYNC_IN => MCU_AUX1, SYNC_OUT => aux1_sync ); Sync_AUX2 : Synchronizer GENERIC MAP(stages => 2) PORT MAP( - CLK => clk160, + CLK => clk_pll, SYNC_IN => MCU_AUX2, SYNC_OUT => aux2_sync ); Sync_AUX3 : Synchronizer GENERIC MAP(stages => 2) PORT MAP( - CLK => clk160, + CLK => clk_pll, SYNC_IN => MCU_AUX3, SYNC_OUT => aux3_sync ); Sync_LO_LD : Synchronizer GENERIC MAP(stages => 2) PORT MAP( - CLK => clk160, + CLK => clk_pll, SYNC_IN => LO1_LD, SYNC_OUT => lo_ld_sync ); Sync_SOURCE_LD : Synchronizer GENERIC MAP(stages => 2) PORT MAP( - CLK => clk160, + CLK => clk_pll, SYNC_IN => SOURCE_LD, SYNC_OUT => source_ld_sync ); Sync_NSS : Synchronizer GENERIC MAP(stages => 2) PORT MAP( - CLK => clk160, + CLK => clk_pll, SYNC_IN => MCU_NSS, SYNC_OUT => nss_sync ); @@ -516,7 +516,7 @@ begin Source: MAX2871 GENERIC MAP(CLK_DIV => 10) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => int_reset, REG4 => source_reg_4, REG3 => source_reg_3, @@ -531,7 +531,7 @@ begin LO1: MAX2871 GENERIC MAP(CLK_DIV => 10) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => int_reset, REG4 => lo_reg_4, REG3 => lo_reg_3, @@ -550,7 +550,7 @@ begin GENERIC MAP(CLK_DIV => 2, CONVCYCLES => 77) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => int_reset, START => adc_trigger_sample, READY => adc_port1_ready, @@ -566,7 +566,7 @@ begin GENERIC MAP(CLK_DIV => 2, CONVCYCLES => 77) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => int_reset, START => adc_trigger_sample, READY => open, -- synchronous ADCs, ready indicated by port 1 ADC @@ -582,7 +582,7 @@ begin GENERIC MAP(CLK_DIV => 2, CONVCYCLES => 77) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => int_reset, START => adc_trigger_sample, READY => open, -- synchronous ADCs, ready indicated by port 1 ADC @@ -597,7 +597,7 @@ begin Windower: Windowing PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => sampling_start, WINDOW_TYPE => sampling_window, PORT1_RAW => adc_port1_data, @@ -614,7 +614,7 @@ begin Sampler: Sampling GENERIC MAP(CLK_CYCLES_PRE_DONE => 0) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => sweep_reset, ADC_PRESCALER => sampling_prescaler, PHASEINC => sampling_phaseinc, @@ -639,7 +639,7 @@ begin sweep_reset <= not aux3_sync; SweepModule: Sweep PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => sweep_reset, NPOINTS => sweep_points, CONFIG_ADDRESS => sweep_config_address, @@ -703,7 +703,7 @@ begin source_unlocked <= not source_ld_sync; SPI: SPICommands PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => int_reset, SCLK => MCU_SCK, MOSI => MCU_MOSI, @@ -755,7 +755,7 @@ begin SA_DFT: DFT GENERIC MAP(BINS => 96) PORT MAP( - CLK => clk160, + CLK => clk_pll, RESET => dft_reset, PORT1 => port1_windowed, PORT2 => port2_windowed, @@ -770,12 +770,12 @@ begin ConfigMem : SweepConfigMem PORT MAP ( - clka => clk160, + clka => clk_pll, ena => '1', wea => sweep_config_write, addra => sweep_config_write_address, dina => sweep_config_write_data, - clkb => clk160, + clkb => clk_pll, addrb => sweep_config_address, doutb => sweep_config_data ); diff --git a/README.md b/README.md index d1dd414..a7146a6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# LibreVNA +![LibreVNA](Software/PC_Application/resources/banner.png) + **100kHz to 6GHz VNA** This is the improved version of my [first attempt](https://www.github.com/jankae/VNA) at a VNA. diff --git a/Software/PC_Application/Calibration/amplitudecaldialog.cpp b/Software/PC_Application/Calibration/amplitudecaldialog.cpp index 50f89d9..766171b 100644 --- a/Software/PC_Application/Calibration/amplitudecaldialog.cpp +++ b/Software/PC_Application/Calibration/amplitudecaldialog.cpp @@ -289,6 +289,9 @@ void AmplitudeCalDialog::AddPointDialog() auto d = new QDialog(); auto ui = new Ui::AddAmplitudePointsDialog(); ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); ui->frequency->setUnit("Hz"); ui->frequency->setPrefixes(" kMG"); ui->startFreq->setUnit("Hz"); @@ -356,6 +359,9 @@ void AmplitudeCalDialog::AutomaticMeasurementDialog() automatic.dialog = new QDialog(this); auto ui = new Ui::AutomaticAmplitudeDialog(); ui->setupUi(automatic.dialog); + connect(automatic.dialog, &QDialog::finished, [=](){ + delete ui; + }); automatic.progress = ui->progress; ui->explanation->setText(info); ui->status->setText("Gathering information about "+otherCal+" Calibration..."); diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index 9029134..bf0710e 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -26,6 +26,8 @@ Calibration::Calibration() measurements[Measurement::Line].datapoints = vector(); type = Type::None; + port1Standard = port2Standard = PortStandard::Male; + throughZeroLength = false; } Calibration::Standard Calibration::getPort1Standard(Calibration::Measurement m) @@ -122,18 +124,28 @@ bool Calibration::constructErrorTerms(Calibration::Type type) } qDebug() << "Constructing error terms for" << TypeToString(type) << "calibration"; bool isTRL = type == Type::TRL; - double kit_minFreq = kit.minFreq(isTRL); - double kit_maxFreq = kit.maxFreq(isTRL); - if(minFreq < kit_minFreq || maxFreq > kit_maxFreq) { + bool uses_male = true; + bool uses_female = true; + if(!kit.checkIfValid(minFreq, maxFreq, isTRL, uses_male, uses_female)) { + // TODO adjust for male/female standards // Calkit does not support complete calibration range QString msg = QString("The calibration kit does not support the complete span.\n\n") + "The measured calibration data covers " + Unit::ToString(minFreq, "Hz", " kMG", 4) + " to " + Unit::ToString(maxFreq, "Hz", " kMG", 4) - + ", however the calibration kit is only valid from " + Unit::ToString(kit_minFreq, "Hz", " kMG", 4) + " to " + Unit::ToString(kit_maxFreq, "Hz", " kMG", 4) + ".\n\n" + + ", however the calibration kit does not support the whole frequency range.\n\n" + "Please adjust the calibration kit or the span and take the calibration measurements again."; InformationBox::ShowError("Unable to perform calibration", msg); qWarning() << msg; return false; } + // check calkit standards and adjust if necessary + if(!kit.hasSeparateMaleFemaleStandards()) { + port1Standard = PortStandard::Male; + port2Standard = PortStandard::Male; + } + if(port1Standard == port2Standard) { + // unable to use zero-length through + throughZeroLength = false; + } switch(type) { case Type::Port1SOL: constructPort1SOL(); break; case Type::Port2SOL: constructPort2SOL(); break; @@ -181,22 +193,34 @@ void Calibration::construct12TermPoints() auto S22_through = complex(measurements[Measurement::Through].datapoints[i].real_S22, measurements[Measurement::Through].datapoints[i].imag_S22); auto S12_through = complex(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12); - auto actual = kit.toSOLT(p.frequency); + auto actual = kit.toSOLT(p.frequency, port1Standard == PortStandard::Male); // 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 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 + if (throughZeroLength) { + // use ideal through + actual.ThroughS11 = 0.0; + actual.ThroughS12 = 1.0; + actual.ThroughS21 = 1.0; + actual.ThroughS22 = 0.0; + } + 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) / ((S11_through - p.fe00)*(actual.ThroughS22-p.fe11*deltaS)-deltaS*p.fe10e01); p.fe10e32 = (S21_through - p.fe30)*(1.0 - p.fe11*actual.ThroughS11 - p.fe22*actual.ThroughS22 + p.fe11*p.fe22*deltaS) / actual.ThroughS21; // Reverse calibration + actual = kit.toSOLT(p.frequency, port2Standard == PortStandard::Male); computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load); p.re03 = S12_isolation; p.re11 = ((S22_through - p.re33)*(1.0 - p.re22 * actual.ThroughS22)-actual.ThroughS22*p.re23e32) / ((S22_through - p.re33)*(actual.ThroughS11-p.re22*deltaS)-deltaS*p.re23e32); p.re23e01 = (S12_through - p.re03)*(1.0 - p.re11*actual.ThroughS11 - p.re22*actual.ThroughS22 + p.re11*p.re22*deltaS) / actual.ThroughS12; + + + points.push_back(p); } } @@ -212,10 +236,11 @@ void Calibration::constructPort1SOL() auto S11_short = complex(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11); auto S11_load = complex(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11); // OSL port1 - auto actual = kit.toSOLT(p.frequency); + auto actual = kit.toSOLT(p.frequency, port1Standard == PortStandard::Male); // 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.fex = 0.0; p.fe30 = 0.0; p.fe22 = 0.0; p.fe10e32 = 1.0; @@ -223,6 +248,7 @@ void Calibration::constructPort1SOL() p.re22 = 0.0; p.re23e32 = 1.0; p.re03 = 0.0; + p.rex = 0.0; p.re11 = 0.0; p.re23e01 = 1.0; points.push_back(p); @@ -240,10 +266,11 @@ void Calibration::constructPort2SOL() auto S22_short = complex(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22); auto S22_load = complex(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22); // OSL port2 - auto actual = kit.toSOLT(p.frequency); + auto actual = kit.toSOLT(p.frequency, port1Standard == PortStandard::Male); // 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.fex = 0.0; p.fe30 = 0.0; p.fe22 = 0.0; p.fe10e32 = 1.0; @@ -251,6 +278,7 @@ void Calibration::constructPort2SOL() p.fe11 = 0.0; p.fe10e01 = 1.0; p.re03 = 0.0; + p.rex = 0.0; p.re11 = 0.0; p.re23e01 = 1.0; points.push_back(p); @@ -271,11 +299,13 @@ void Calibration::constructTransmissionNormalization() p.re23e01 = S12_through / actual.ThroughS12; // All other calibration coefficients to ideal values p.fe30 = 0.0; + p.fex = 0.0; p.fe22 = 0.0; p.fe00 = 0.0; p.fe11 = 0.0; p.fe10e01 = 1.0; p.re03 = 0.0; + p.rex = 0.0; p.re11 = 0.0; p.re33 = 0.0; p.re22 = 0.0; @@ -375,6 +405,7 @@ void Calibration::constructTRL() p.fe10e32 = S_B.m21; // no isolation measurement available p.fe30 = 0.0; + p.fex = 0.0; // Reverse coefficients, normalize for S12 = 1.0 // => det(T)/T22 = 1.0 @@ -395,6 +426,7 @@ void Calibration::constructTRL() p.re33 = S_B.m22; // no isolation measurement available p.re03 = 0.0; + p.rex = 0.0; points.push_back(p); } @@ -774,11 +806,23 @@ bool Calibration::openFromFile(QString filename) } try { - file >> *this; + nlohmann::json j; + file >> j; + fromJSON(j); } catch(exception e) { - InformationBox::ShowError("File parsing error", e.what()); - qWarning() << "Calibration file parsing failed: " << e.what(); - return false; + // json parsing failed, probably using a legacy file format + try { + file.clear(); + file.seekg(0); + file >> *this; + InformationBox::ShowMessage("Loading calibration file", "The file \"" + filename + "\" is stored in a deprecated" + " calibration format. Future versions of this application might not support" + " it anymore. Please save the calibration to update to the new format"); + } catch(exception e) { + InformationBox::ShowError("File parsing error", e.what()); + qWarning() << "Calibration file parsing failed: " << e.what(); + return false; + } } this->currentCalFile = filename; // if all ok, remember this @@ -802,7 +846,7 @@ bool Calibration::saveToFile(QString filename) auto calibration_file = filename + ".cal"; ofstream file; file.open(calibration_file.toStdString()); - file << *this; + file << setw(1) << toJSON(); auto calkit_file = filename + ".calkit"; qDebug() << "Saving associated calibration kit to file" << calkit_file; @@ -839,6 +883,16 @@ QString Calibration::descriptiveCalName(){ return tmp; } +bool Calibration::getThroughZeroLength() const +{ + return throughZeroLength; +} + +void Calibration::setThroughZeroLength(bool value) +{ + throughZeroLength = value; +} + double Calibration::getMinFreq(){ return this->minFreq; } @@ -848,6 +902,97 @@ double Calibration::getMaxFreq(){ int Calibration::getNumPoints(){ return this->points.size(); } + +nlohmann::json Calibration::toJSON() +{ + nlohmann::json j; + nlohmann::json j_measurements; + for(auto m : measurements) { + if(m.second.datapoints.size() > 0) { + nlohmann::json j_measurement; + j_measurement["name"] = MeasurementToString(m.first).toStdString(); + j_measurement["timestamp"] = m.second.timestamp.toSecsSinceEpoch(); + nlohmann::json j_points; + for(auto p : m.second.datapoints) { + nlohmann::json j_point; + j_point["frequency"] = p.frequency; + j_point["S11_real"] = p.real_S11; + j_point["S11_imag"] = p.imag_S11; + j_point["S12_real"] = p.real_S12; + j_point["S12_imag"] = p.imag_S12; + j_point["S21_real"] = p.real_S21; + j_point["S21_imag"] = p.imag_S21; + j_point["S22_real"] = p.real_S22; + j_point["S22_imag"] = p.imag_S22; + j_points.push_back(j_point); + } + j_measurement["points"] = j_points; + j_measurements.push_back(j_measurement); + } + } + j["measurements"] = j_measurements; + j["type"] = TypeToString(getType()).toStdString(); + j["port1StandardMale"] = port1Standard == PortStandard::Male; + j["port2StandardMale"] = port2Standard == PortStandard::Male; + j["throughZeroLength"] = throughZeroLength; + + return j; +} + +void Calibration::fromJSON(nlohmann::json j) +{ + clearMeasurements(); + resetErrorTerms(); + port1Standard = j.value("port1StandardMale", true) ? PortStandard::Male : PortStandard::Female; + port2Standard = j.value("port2StandardMale", true) ? PortStandard::Male : PortStandard::Female; + throughZeroLength = j.value("throughZeroLength", false); + if(j.contains("measurements")) { + // grab measurements + for(auto j_m : j["measurements"]) { + if(!j_m.contains("name")) { + throw runtime_error("Measurement without name given"); + } + auto m = MeasurementFromString(QString::fromStdString(j_m["name"])); + if(m == Measurement::Last) { + throw runtime_error("Measurement name unknown: "+std::string(j_m["name"])); + } + // get timestamp + measurements[m].timestamp = QDateTime::fromSecsSinceEpoch(j_m.value("timestamp", 0)); + // extract points + if(!j_m.contains("points")) { + throw runtime_error("Measurement "+MeasurementToString(m).toStdString()+" does not contain any points"); + } + int pointNum = 0; + for(auto j_p : j_m["points"]) { + Protocol::Datapoint p; + p.pointNum = pointNum++; + p.frequency = j_p.value("frequency", 0.0); + p.real_S11 = j_p.value("S11_real", 0.0); + p.imag_S11 = j_p.value("S11_imag", 0.0); + p.real_S12 = j_p.value("S12_real", 0.0); + p.imag_S12 = j_p.value("S12_imag", 0.0); + p.real_S21 = j_p.value("S21_real", 0.0); + p.imag_S21 = j_p.value("S21_imag", 0.0); + p.real_S22 = j_p.value("S22_real", 0.0); + p.imag_S22 = j_p.value("S22_imag", 0.0); + measurements[m].datapoints.push_back(p); + } + } + } + // got all measurements, construct calibration according to type + if(j.contains("type")) { + auto t = TypeFromString(QString::fromStdString(j["type"])); + if(t == Type::Last) { + throw runtime_error("Calibration type unknown: "+std::string(j["type"])); + } + if(calculationPossible(t)) { + constructErrorTerms(t); + } else { + throw runtime_error("Incomplete calibration data, the requested calibration could not be performed."); + } + } +} + QString Calibration::getCurrentCalibrationFile(){ return this->currentCalFile; } @@ -872,6 +1017,11 @@ ostream& operator<<(ostream &os, const Calibration &c) istream& operator >>(istream &in, Calibration &c) { + // old file format did not contain port standard gender, set default + c.port1Standard = Calibration::PortStandard::Male; + c.port2Standard = Calibration::PortStandard::Male; + c.throughZeroLength = false; + std::string line; while(getline(in, line)) { QString qLine = QString::fromStdString(line).simplified(); @@ -978,7 +1128,9 @@ Calibration::Point Calibration::getCalibrationPoint(Protocol::Datapoint &d) ret.fe11 = low->fe11 * (1 - alpha) + high->fe11 * alpha; ret.fe22 = low->fe22 * (1 - alpha) + high->fe22 * alpha; ret.fe30 = low->fe30 * (1 - alpha) + high->fe30 * alpha; + ret.fex = low->fex * (1 - alpha) + high->fex * alpha; ret.re03 = low->re03 * (1 - alpha) + high->re03 * alpha; + ret.rex = low->rex * (1 - alpha) + high->rex * alpha; ret.re11 = low->re11 * (1 - alpha) + high->re11 * alpha; ret.re22 = low->re22 * (1 - alpha) + high->re22 * alpha; ret.re33 = low->re33 * (1 - alpha) + high->re33 * alpha; @@ -1002,6 +1154,12 @@ void Calibration::computeSOL(std::complex s_m, std::complex o_m, tracking = directivity * match - delta; } +void Calibration::computeIsolation(std::complex x0_m, std::complex x1_m, std::complex reverse_match, std::complex reverse_tracking, std::complex reverse_directivity, std::complex x0, std::complex x1, std::complex &internal_isolation, std::complex &external_isolation) +{ + external_isolation = (x1_m - x0_m)*(1.0 - reverse_match * (x1 - x0) + x1*x0*reverse_match*reverse_match) / (reverse_tracking * (x1 - x0)); + internal_isolation = x0_m - external_isolation*(reverse_directivity + reverse_tracking*x0 / (1.0 - x0*reverse_match)); +} + std::complex Calibration::correctSOL(std::complex measured, std::complex directivity, std::complex match, std::complex tracking) { return (measured - directivity) / (measured * match - directivity * match + tracking); @@ -1017,6 +1175,26 @@ void Calibration::setCalibrationKit(const Calkit &value) kit = value; } +void Calibration::setPortStandard(int port, Calibration::PortStandard standard) +{ + if(port == 1) { + port1Standard = standard; + } else if(port == 2) { + port2Standard = standard; + } +} + +Calibration::PortStandard Calibration::getPortStandard(int port) +{ + if(port == 1) { + return port1Standard; + } else if(port == 2) { + return port2Standard; + } else { + return PortStandard::Male; + } +} + Calibration::Type Calibration::getType() const { return type; diff --git a/Software/PC_Application/Calibration/calibration.h b/Software/PC_Application/Calibration/calibration.h index 50f0234..fba9643 100644 --- a/Software/PC_Application/Calibration/calibration.h +++ b/Software/PC_Application/Calibration/calibration.h @@ -11,8 +11,9 @@ #include #include #include +#include -class Calibration +class Calibration : public Savable { public: Calibration(); @@ -108,6 +109,23 @@ public: Calkit& getCalibrationKit(); void setCalibrationKit(const Calkit &value); + enum class PortStandard { + Male, + Female, + }; + void setPortStandard(int port, PortStandard standard); + PortStandard getPortStandard(int port); + bool getThroughZeroLength() const; + void setThroughZeroLength(bool value); + + QString getCurrentCalibrationFile(); + double getMinFreq(); + double getMaxFreq(); + int getNumPoints(); + + nlohmann::json toJSON() override; + void fromJSON(nlohmann::json j) override; + private: void construct12TermPoints(); void constructPort1SOL(); @@ -120,9 +138,9 @@ private: public: double frequency; // Forward error terms - std::complex fe00, fe11, fe10e01, fe10e32, fe22, fe30; + std::complex fe00, fe11, fe10e01, fe10e32, fe22, fe30, fex; // Reverse error terms - std::complex re33, re11, re23e32, re23e01, re22, re03; + std::complex re33, re11, re23e32, re23e01, re22, re03, rex; }; Point getCalibrationPoint(Protocol::Datapoint &d); /* @@ -140,6 +158,15 @@ private: std::complex o_c = std::complex(1.0, 0), std::complex s_c = std::complex(-1.0, 0), std::complex l_c = std::complex(0, 0)); + void computeIsolation(std::complex x0_m, + std::complex x1_m, + std::complex reverse_match, + std::complex reverse_tracking, + std::complex reverse_directivity, + std::complex x0, + std::complex x1, + std::complex &internal_isolation, + std::complex &external_isolation); std::complex correctSOL(std::complex measured, std::complex directivity, std::complex match, @@ -157,14 +184,10 @@ private: Calkit kit; QString descriptiveCalName(); - -private: QString currentCalFile; -public: - QString getCurrentCalibrationFile(); - double getMinFreq(); - double getMaxFreq(); - int getNumPoints(); + + PortStandard port1Standard, port2Standard; + bool throughZeroLength; }; #endif // CALIBRATION_H diff --git a/Software/PC_Application/Calibration/calibrationtracedialog.cpp b/Software/PC_Application/Calibration/calibrationtracedialog.cpp index 434dbe5..6aefba5 100644 --- a/Software/PC_Application/Calibration/calibrationtracedialog.cpp +++ b/Software/PC_Application/Calibration/calibrationtracedialog.cpp @@ -21,16 +21,91 @@ CalibrationTraceDialog::CalibrationTraceDialog(Calibration *cal, double f_min, d model = new MeasurementModel(cal, measurements); ui->tableView->setModel(model); ui->tableView->setColumnWidth(0, 100); - ui->tableView->setColumnWidth(1, 350); - ui->tableView->setColumnWidth(2, 320); - ui->tableView->setColumnWidth(3, 160); + ui->tableView->setColumnWidth(1, 80); + ui->tableView->setColumnWidth(2, 350); + ui->tableView->setColumnWidth(3, 320); + ui->tableView->setColumnWidth(4, 160); UpdateCalibrationStatus(); + auto updateThroughStandardUI = [=](){ + if(cal->getPortStandard(1) == cal->getPortStandard(2)) { + // same gender on both ports, can't use zero length through + ui->throughCalkit->click(); + ui->throughZero->setEnabled(false); + ui->throughCalkit->setEnabled(false); + } else { + // user may select option for through + ui->throughZero->setEnabled(true); + ui->throughCalkit->setEnabled(true); + } + model->genderUpdated(); + }; + + connect(ui->port1Group, qOverload(&QButtonGroup::buttonClicked), [=](){ + if(ui->port1Male->isChecked()) { + cal->setPortStandard(1, Calibration::PortStandard::Male); + } else { + cal->setPortStandard(1, Calibration::PortStandard::Female); + } + updateThroughStandardUI(); + UpdateCalibrationStatus(); + }); + + connect(ui->port2Group, qOverload(&QButtonGroup::buttonClicked), [=](){ + if(ui->port2Male->isChecked()) { + cal->setPortStandard(2, Calibration::PortStandard::Male); + } else { + cal->setPortStandard(2, Calibration::PortStandard::Female); + } + updateThroughStandardUI(); + UpdateCalibrationStatus(); + }); + + connect(ui->throughGroup, qOverload(&QButtonGroup::buttonClicked), [=](){ + if(ui->throughZero->isChecked()) { + cal->setThroughZeroLength(true); + } else { + cal->setThroughZeroLength(false); + } + UpdateCalibrationStatus(); + }); + + // hide selector if calkit does not have separate male/female standards + if(!cal->getCalibrationKit().hasSeparateMaleFemaleStandards()) { + ui->port1Standards->hide(); + ui->port2Standards->hide(); + ui->throughStandard->hide(); + ui->tableView->hideColumn((int) MeasurementModel::ColIndex::Gender); + // default selection is male + ui->port1Male->click(); + ui->port2Male->click(); + ui->throughCalkit->click(); + } else { + // separate standards defined + if(cal->getPortStandard(1) == Calibration::PortStandard::Male) { + ui->port1Male->setChecked(true); + } else { + ui->port1Female->setChecked(true); + } + if(cal->getPortStandard(2) == Calibration::PortStandard::Male) { + ui->port2Male->setChecked(true); + } else { + ui->port2Female->setChecked(true); + } + if(cal->getThroughZeroLength()) { + ui->throughZero->setChecked(true); + } else { + ui->throughCalkit->setChecked(true); + } + updateThroughStandardUI(); + } + // Check calibration kit span if(type != Calibration::Type::None) { auto kit = cal->getCalibrationKit(); auto isTRL = type == Calibration::Type::TRL; - if(kit.minFreq(isTRL) > f_min || kit.maxFreq(isTRL) < f_max) { + if(isTRL && (kit.minFreqTRL() > f_min || kit.maxFreqTRL() < f_max)) { + // TODO check SOLT frequency range depending on selected male/female kit InformationBox::ShowMessage("Warning", "The calibration kit does not completely cover the currently selected span. " "Applying a calibration will not be possible for any measurements taken with these settings."); } @@ -70,9 +145,11 @@ void CalibrationTraceDialog::UpdateCalibrationStatus() void CalibrationTraceDialog::on_bDelete_clicked() { - auto measurement = measurements[ui->tableView->currentIndex().row()]; - cal->clearMeasurement(measurement); - model->measurementUpdated(measurement); + auto selected = ui->tableView->selectionModel()->selectedRows(); + for(auto s : selected) { + cal->clearMeasurement(measurements[s.row()]); + model->measurementUpdated(measurements[s.row()]); + } UpdateCalibrationStatus(); } diff --git a/Software/PC_Application/Calibration/calibrationtracedialog.ui b/Software/PC_Application/Calibration/calibrationtracedialog.ui index 33c30c5..58dd531 100644 --- a/Software/PC_Application/Calibration/calibrationtracedialog.ui +++ b/Software/PC_Application/Calibration/calibrationtracedialog.ui @@ -16,80 +16,180 @@ true - + - + - - - QAbstractItemView::SelectRows + + + Port 1 Standards - - true - - - false - - - true - - - false - - - false - + + + + + Male + + + port1Group + + + + + + + Female + + + port1Group + + + + - - - - - Measure - - - - :/icons/play.png:/icons/play.png - - - - - - - Delete - - - - :/icons/trash.png:/icons/trash.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Apply Calibration - - - - :/icons/ok.png:/icons/ok.png - - - - + + + Port 2 Standards + + + + + + Male + + + port2Group + + + + + + + Female + + + port2Group + + + + + + + + + + Through Standard + + + + + + From calibration kit + + + throughGroup + + + + + + + Zero-length through + + + throughGroup + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + false + + + false + + + + + + + + + Measure + + + + :/icons/play.png:/icons/play.png + + + + + + + Delete + + + + :/icons/trash.png:/icons/trash.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply Calibration + + + + :/icons/ok.png:/icons/ok.png + + @@ -99,4 +199,9 @@ + + + + + diff --git a/Software/PC_Application/Calibration/calkit.cpp b/Software/PC_Application/Calibration/calkit.cpp index 890d9a2..e039593 100644 --- a/Software/PC_Application/Calibration/calkit.cpp +++ b/Software/PC_Application/Calibration/calkit.cpp @@ -14,9 +14,12 @@ using json = nlohmann::json; using namespace std; Calkit::Calkit() - : ts_open(nullptr), - ts_short(nullptr), - ts_load(nullptr), + : ts_open_m(nullptr), + ts_short_m(nullptr), + ts_load_m(nullptr), + ts_open_f(nullptr), + ts_short_f(nullptr), + ts_load_f(nullptr), ts_through(nullptr), ts_cached(false) { @@ -128,39 +131,39 @@ Calkit Calkit::fromFile(QString filename) // legacy file format, return to beginning of file file.clear(); file.seekg(0); - c.SOLT.Open.useMeasurements = readLine(file).toInt(); - c.SOLT.Short.useMeasurements = readLine(file).toInt(); - c.SOLT.Load.useMeasurements = readLine(file).toInt(); + c.SOLT.open_m.useMeasurements = readLine(file).toInt(); + c.SOLT.short_m.useMeasurements = readLine(file).toInt(); + c.SOLT.load_m.useMeasurements = readLine(file).toInt(); c.SOLT.Through.useMeasurements = readLine(file).toInt(); - c.SOLT.Open.Z0 = readLine(file).toDouble(); - c.SOLT.Open.delay = readLine(file).toDouble(); - c.SOLT.Open.loss = readLine(file).toDouble(); - c.SOLT.Open.C0 = readLine(file).toDouble(); - c.SOLT.Open.C1 = readLine(file).toDouble(); - c.SOLT.Open.C2 = readLine(file).toDouble(); - c.SOLT.Open.C3 = readLine(file).toDouble(); - c.SOLT.Short.Z0 = readLine(file).toDouble(); - c.SOLT.Short.delay = readLine(file).toDouble(); - c.SOLT.Short.loss = readLine(file).toDouble(); - c.SOLT.Short.L0 = readLine(file).toDouble(); - c.SOLT.Short.L1 = readLine(file).toDouble(); - c.SOLT.Short.L2 = readLine(file).toDouble(); - c.SOLT.Short.L3 = readLine(file).toDouble(); - c.SOLT.Load.Z0 = readLine(file).toDouble(); + c.SOLT.open_m.Z0 = readLine(file).toDouble(); + c.SOLT.open_m.delay = readLine(file).toDouble(); + c.SOLT.open_m.loss = readLine(file).toDouble(); + c.SOLT.open_m.C0 = readLine(file).toDouble(); + c.SOLT.open_m.C1 = readLine(file).toDouble(); + c.SOLT.open_m.C2 = readLine(file).toDouble(); + c.SOLT.open_m.C3 = readLine(file).toDouble(); + c.SOLT.short_m.Z0 = readLine(file).toDouble(); + c.SOLT.short_m.delay = readLine(file).toDouble(); + c.SOLT.short_m.loss = readLine(file).toDouble(); + c.SOLT.short_m.L0 = readLine(file).toDouble(); + c.SOLT.short_m.L1 = readLine(file).toDouble(); + c.SOLT.short_m.L2 = readLine(file).toDouble(); + c.SOLT.short_m.L3 = readLine(file).toDouble(); + c.SOLT.load_m.Z0 = readLine(file).toDouble(); c.SOLT.Through.Z0 = readLine(file).toDouble(); c.SOLT.Through.delay = readLine(file).toDouble(); c.SOLT.Through.loss = readLine(file).toDouble(); - if(c.SOLT.Open.useMeasurements) { - c.SOLT.Open.file = readLine(file); - c.SOLT.Open.Sparam = readLine(file).toInt(); + if(c.SOLT.open_m.useMeasurements) { + c.SOLT.open_m.file = readLine(file); + c.SOLT.open_m.Sparam = readLine(file).toInt(); } - if(c.SOLT.Short.useMeasurements) { - c.SOLT.Short.file = readLine(file); - c.SOLT.Short.Sparam = readLine(file).toInt(); + if(c.SOLT.short_m.useMeasurements) { + c.SOLT.short_m.file = readLine(file); + c.SOLT.short_m.Sparam = readLine(file).toInt(); } - if(c.SOLT.Load.useMeasurements) { - c.SOLT.Load.file = readLine(file); - c.SOLT.Load.Sparam = readLine(file).toInt(); + if(c.SOLT.load_m.useMeasurements) { + c.SOLT.load_m.file = readLine(file); + c.SOLT.load_m.Sparam = readLine(file).toInt(); } if(c.SOLT.Through.useMeasurements) { c.SOLT.Through.file = readLine(file); @@ -173,6 +176,8 @@ Calkit Calkit::fromFile(QString filename) c.TRL.Line.minFreq = readLine(file).toDouble(); c.TRL.Line.maxFreq = readLine(file).toDouble(); + c.SOLT.separate_male_female = false; + InformationBox::ShowMessage("Loading calkit file", "The file \"" + filename + "\" is stored in a deprecated" " calibration kit format. Future versions of this application might not support" " it anymore. Please save the calibration kit to update to the new format"); @@ -199,7 +204,12 @@ void Calkit::edit(std::function done) dialog->show(); } -class Calkit::SOLT Calkit::toSOLT(double frequency) +bool Calkit::hasSeparateMaleFemaleStandards() +{ + return SOLT.separate_male_female; +} + +class Calkit::SOLT Calkit::toSOLT(double frequency, bool male_standards) { auto addTransmissionLine = [](complex termination_reflection, double offset_impedance, double offset_delay, double offset_loss, double frequency) -> complex { // nomenclature and formulas from https://loco.lab.asu.edu/loco-memos/edges_reports/report_20130807.pdf @@ -221,29 +231,36 @@ class Calkit::SOLT Calkit::toSOLT(double frequency) return Gamma_i; }; + auto Load = male_standards ? SOLT.load_m : SOLT.load_f; + auto Short = male_standards ? SOLT.short_m : SOLT.short_f; + auto Open = male_standards ? SOLT.open_m : SOLT.open_f; + auto ts_load = male_standards ? ts_load_m : ts_load_f; + auto ts_short = male_standards ? ts_short_m : ts_short_f; + auto ts_open = male_standards ? ts_open_m : ts_open_f; + fillTouchstoneCache(); class SOLT ref; - if(SOLT.Load.useMeasurements) { + if(Load.useMeasurements) { ref.Load = ts_load->interpolate(frequency).S[0]; } else { - auto imp_load = complex(SOLT.Load.Z0, 0); + auto imp_load = complex(Load.Z0, 0); // Add parallel capacitor to impedance - if(SOLT.Load.Cparallel > 0) { - auto imp_C = complex(0, -1.0 / (frequency * 2 * M_PI * SOLT.Load.Cparallel)); + if(Load.Cparallel > 0) { + auto imp_C = complex(0, -1.0 / (frequency * 2 * M_PI * Load.Cparallel)); imp_load = (imp_load * imp_C) / (imp_load + imp_C); } // add series inductor to impedance - auto imp_L = complex(0, frequency * 2 * M_PI * SOLT.Load.Lseries); + auto imp_L = complex(0, frequency * 2 * M_PI * Load.Lseries); imp_load += imp_L; ref.Load = (imp_load - complex(50.0)) / (imp_load + complex(50.0)); - ref.Load = addTransmissionLine(ref.Load, SOLT.Load.Z0, SOLT.Load.delay*1e-12, 0, frequency); + ref.Load = addTransmissionLine(ref.Load, Load.Z0, Load.delay*1e-12, 0, frequency); } - if(SOLT.Open.useMeasurements) { + if(Open.useMeasurements) { ref.Open = ts_open->interpolate(frequency).S[0]; } else { // calculate fringing capacitance for open - double Cfringing = SOLT.Open.C0 * 1e-15 + SOLT.Open.C1 * 1e-27 * frequency + SOLT.Open.C2 * 1e-36 * pow(frequency, 2) + SOLT.Open.C3 * 1e-45 * pow(frequency, 3); + double Cfringing = Open.C0 * 1e-15 + Open.C1 * 1e-27 * frequency + Open.C2 * 1e-36 * pow(frequency, 2) + Open.C3 * 1e-45 * pow(frequency, 3); // convert to impedance if (Cfringing == 0) { // special case to avoid issues with infinity @@ -252,18 +269,18 @@ class Calkit::SOLT Calkit::toSOLT(double frequency) auto imp_open = complex(0, -1.0 / (frequency * 2 * M_PI * Cfringing)); ref.Open = (imp_open - complex(50.0)) / (imp_open + complex(50.0)); } - ref.Open = addTransmissionLine(ref.Open, SOLT.Open.Z0, SOLT.Open.delay*1e-12, SOLT.Open.loss*1e9, frequency); + ref.Open = addTransmissionLine(ref.Open, Open.Z0, Open.delay*1e-12, Open.loss*1e9, frequency); } - if(SOLT.Short.useMeasurements) { + if(Short.useMeasurements) { ref.Short = ts_short->interpolate(frequency).S[0]; } else { // calculate inductance for short - double Lseries = SOLT.Short.L0 * 1e-12 + SOLT.Short.L1 * 1e-24 * frequency + SOLT.Short.L2 * 1e-33 * pow(frequency, 2) + SOLT.Short.L3 * 1e-42 * pow(frequency, 3); + double Lseries = Short.L0 * 1e-12 + Short.L1 * 1e-24 * frequency + Short.L2 * 1e-33 * pow(frequency, 2) + Short.L3 * 1e-42 * pow(frequency, 3); // convert to impedance auto imp_short = complex(0, frequency * 2 * M_PI * Lseries); ref.Short = (imp_short - complex(50.0)) / (imp_short + complex(50.0)); - ref.Short = addTransmissionLine(ref.Short, SOLT.Short.Z0, SOLT.Short.delay*1e-12, SOLT.Short.loss*1e9, frequency); + ref.Short = addTransmissionLine(ref.Short, Short.Z0, Short.delay*1e-12, Short.loss*1e9, frequency); } if(SOLT.Through.useMeasurements) { @@ -300,47 +317,91 @@ class Calkit::TRL Calkit::toTRL(double) return trl; } -double Calkit::minFreq(bool trl) +double Calkit::minFreqTRL() { - if(trl) { - return TRL.Line.minFreq; - } else { - fillTouchstoneCache(); - double min = 0; - array 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 TRL.Line.minFreq; } -double Calkit::maxFreq(bool trl) +double Calkit::maxFreqTRL() { - if(trl) { - return TRL.Line.maxFreq; + return TRL.Line.maxFreq; +} + +double Calkit::minFreqSOLT(bool male_standards) +{ + fillTouchstoneCache(); + double min = 0; + auto ts_load = male_standards ? ts_load_m : ts_load_f; + auto ts_short = male_standards ? ts_short_m : ts_short_f; + auto ts_open = male_standards ? ts_open_m : ts_open_f; + array 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; +} + +double Calkit::maxFreqSOLT(bool male_standards) +{ + fillTouchstoneCache(); + double max = std::numeric_limits::max(); + auto ts_load = male_standards ? ts_load_m : ts_load_f; + auto ts_short = male_standards ? ts_short_m : ts_short_f; + auto ts_open = male_standards ? ts_open_m : ts_open_f; + array 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::checkIfValid(double min_freq, double max_freq, bool isTRL, bool include_male, bool include_female) +{ + auto min_supported = std::numeric_limits::min(); + auto max_supported = std::numeric_limits::max(); + if(isTRL) { + min_supported = TRL.Line.minFreq; + max_supported = TRL.Line.maxFreq; } else { - fillTouchstoneCache(); - double max = std::numeric_limits::max(); - array 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(include_male) { + auto min_male = minFreqSOLT(true); + auto max_male = maxFreqSOLT(true); + if(min_male > min_supported) { + min_supported = min_male; } - if(ts->maxFreq() < max) { - max = ts->maxFreq(); + if(max_male > max_supported) { + max_supported = max_male; } } - return max; + if(include_female) { + auto min_female = minFreqSOLT(false); + auto max_female = maxFreqSOLT(false); + if(min_female > min_supported) { + min_supported = min_female; + } + if(max_female > max_supported) { + max_supported = max_female; + } + } + } + if(min_supported <= min_freq && max_supported >= max_freq) { + return true; + } else { + return false; } } @@ -351,7 +412,7 @@ bool Calkit::isTRLReflectionShort() const void Calkit::TransformPathsToRelative(QFileInfo d) { - vector filenames = {&SOLT.Short.file, &SOLT.Open.file, &SOLT.Load.file, &SOLT.Through.file}; + vector filenames = {&SOLT.short_m.file, &SOLT.open_m.file, &SOLT.load_m.file, &SOLT.short_f.file, &SOLT.open_f.file, &SOLT.load_f.file, &SOLT.Through.file}; for(auto f : filenames) { if(f->isEmpty()) { continue; @@ -366,7 +427,7 @@ void Calkit::TransformPathsToRelative(QFileInfo d) void Calkit::TransformPathsToAbsolute(QFileInfo d) { - vector filenames = {&SOLT.Short.file, &SOLT.Open.file, &SOLT.Load.file, &SOLT.Through.file}; + vector filenames = {&SOLT.short_m.file, &SOLT.open_m.file, &SOLT.load_m.file, &SOLT.short_f.file, &SOLT.open_f.file, &SOLT.load_f.file, &SOLT.Through.file}; for(auto f : filenames) { if(f->isEmpty()) { continue; @@ -382,12 +443,18 @@ void Calkit::TransformPathsToAbsolute(QFileInfo d) void Calkit::clearTouchstoneCache() { - delete ts_open; - ts_open = nullptr; - delete ts_short; - ts_short = nullptr; - delete ts_load; - ts_load = nullptr; + delete ts_open_m; + ts_open_m = nullptr; + delete ts_short_m; + ts_short_m = nullptr; + delete ts_load_m; + ts_load_m = nullptr; + delete ts_open_f; + ts_open_f = nullptr; + delete ts_short_f; + ts_short_f = nullptr; + delete ts_load_f; + ts_load_f = nullptr; delete ts_through; ts_through = nullptr; ts_cached = false; @@ -398,20 +465,35 @@ void Calkit::fillTouchstoneCache() if(ts_cached) { return; } - if(SOLT.Open.useMeasurements) { - ts_open = new Touchstone(1); - *ts_open = Touchstone::fromFile(SOLT.Open.file.toStdString()); - ts_open->reduceTo1Port(SOLT.Open.Sparam); + if(SOLT.open_m.useMeasurements) { + ts_open_m = new Touchstone(1); + *ts_open_m = Touchstone::fromFile(SOLT.open_m.file.toStdString()); + ts_open_m->reduceTo1Port(SOLT.open_m.Sparam); } - if(SOLT.Short.useMeasurements) { - ts_short = new Touchstone(1); - *ts_short = Touchstone::fromFile(SOLT.Short.file.toStdString()); - ts_short->reduceTo1Port(SOLT.Short.Sparam); + if(SOLT.short_m.useMeasurements) { + ts_short_m = new Touchstone(1); + *ts_short_m = Touchstone::fromFile(SOLT.short_m.file.toStdString()); + ts_short_m->reduceTo1Port(SOLT.short_m.Sparam); } - if(SOLT.Load.useMeasurements) { - ts_load = new Touchstone(1); - *ts_load = Touchstone::fromFile(SOLT.Load.file.toStdString()); - ts_load->reduceTo1Port(SOLT.Load.Sparam); + if(SOLT.load_m.useMeasurements) { + ts_load_m = new Touchstone(1); + *ts_load_m = Touchstone::fromFile(SOLT.load_m.file.toStdString()); + ts_load_m->reduceTo1Port(SOLT.load_m.Sparam); + } + if(SOLT.open_f.useMeasurements) { + ts_open_f = new Touchstone(1); + *ts_open_f = Touchstone::fromFile(SOLT.open_f.file.toStdString()); + ts_open_f->reduceTo1Port(SOLT.open_f.Sparam); + } + if(SOLT.short_f.useMeasurements) { + ts_short_f = new Touchstone(1); + *ts_short_f = Touchstone::fromFile(SOLT.short_f.file.toStdString()); + ts_short_f->reduceTo1Port(SOLT.short_f.Sparam); + } + if(SOLT.load_f.useMeasurements) { + ts_load_f = new Touchstone(1); + *ts_load_f = Touchstone::fromFile(SOLT.load_f.file.toStdString()); + ts_load_f->reduceTo1Port(SOLT.load_f.Sparam); } if(SOLT.Through.useMeasurements) { ts_through = new Touchstone(2); diff --git a/Software/PC_Application/Calibration/calkit.h b/Software/PC_Application/Calibration/calkit.h index 093fbeb..c75d253 100644 --- a/Software/PC_Application/Calibration/calkit.h +++ b/Software/PC_Application/Calibration/calkit.h @@ -41,10 +41,14 @@ public: void toFile(QString filename); static Calkit fromFile(QString filename); void edit(std::function done = nullptr); - SOLT toSOLT(double frequency); + bool hasSeparateMaleFemaleStandards(); + SOLT toSOLT(double frequency, bool male_standards = true); TRL toTRL(double frequency); - double minFreq(bool trl = false); - double maxFreq(bool trl = false); + double minFreqTRL(); + double maxFreqTRL(); + double minFreqSOLT(bool male_standards = true); + double maxFreqSOLT(bool male_standards = true); + bool checkIfValid(double min_freq, double max_freq, bool isTRL, bool include_male, bool include_female); bool isTRLReflectionShort() const; private: @@ -54,30 +58,34 @@ private: QString manufacturer, serialnumber, description; // SOLT standard definitions struct { - struct { + using Open = struct { double Z0, delay, loss, C0, C1, C2, C3; QString file; bool useMeasurements; int Sparam; - } Open; - struct { + }; + Open open_m, open_f; + using Short = struct { double Z0, delay, loss, L0, L1, L2, L3; QString file; bool useMeasurements; int Sparam; - } Short; - struct { + }; + Short short_m, short_f; + using Load = struct { double Z0, delay, Cparallel, Lseries; QString file; bool useMeasurements; int Sparam; - } Load; + }; + Load load_m, load_f; struct { double Z0, delay, loss; QString file; bool useMeasurements; int Sparam1, Sparam2; } Through; + bool separate_male_female; } SOLT; struct { struct { @@ -92,7 +100,9 @@ private: } TRL; bool startDialogWithSOLT; - Touchstone *ts_open, *ts_short, *ts_load, *ts_through; + Touchstone *ts_open_m, *ts_short_m, *ts_load_m; + Touchstone *ts_open_f, *ts_short_f, *ts_load_f; + Touchstone *ts_through; bool ts_cached; using JSONDescription = struct _jsondescr { @@ -100,40 +110,67 @@ private: QString name; QVariant def; }; - const std::array json_descr = {{ + const std::array json_descr = {{ {&manufacturer, "Manufacturer", ""}, {&serialnumber, "Serialnumber", ""}, {&description, "Description", ""}, - {&SOLT.Open.Z0, "SOLT/Open/Param/Z0", 50.0}, - {&SOLT.Open.delay, "SOLT/Open/Param/Delay", 0.0}, - {&SOLT.Open.loss, "SOLT/Open/Param/Loss", 0.0}, - {&SOLT.Open.C0, "SOLT/Open/Param/C0", 0.0}, - {&SOLT.Open.C1, "SOLT/Open/Param/C1", 0.0}, - {&SOLT.Open.C2, "SOLT/Open/Param/C2", 0.0}, - {&SOLT.Open.C3, "SOLT/Open/Param/C3", 0.0}, - {&SOLT.Open.useMeasurements, "SOLT/Open/Measurements/Use", false}, - {&SOLT.Open.file, "SOLT/Open/Measurements/File", ""}, - {&SOLT.Open.Sparam, "SOLT/Open/Measurements/Port", 0}, + {&SOLT.open_m.Z0, "SOLT/Open/Param/Z0", 50.0}, + {&SOLT.open_m.delay, "SOLT/Open/Param/Delay", 0.0}, + {&SOLT.open_m.loss, "SOLT/Open/Param/Loss", 0.0}, + {&SOLT.open_m.C0, "SOLT/Open/Param/C0", 0.0}, + {&SOLT.open_m.C1, "SOLT/Open/Param/C1", 0.0}, + {&SOLT.open_m.C2, "SOLT/Open/Param/C2", 0.0}, + {&SOLT.open_m.C3, "SOLT/Open/Param/C3", 0.0}, + {&SOLT.open_m.useMeasurements, "SOLT/Open/Measurements/Use", false}, + {&SOLT.open_m.file, "SOLT/Open/Measurements/File", ""}, + {&SOLT.open_m.Sparam, "SOLT/Open/Measurements/Port", 0}, + {&SOLT.open_f.Z0, "SOLT/Open/Param/Z0_Female", 50.0}, + {&SOLT.open_f.delay, "SOLT/Open/Param/Delay_Female", 0.0}, + {&SOLT.open_f.loss, "SOLT/Open/Param/Loss_Female", 0.0}, + {&SOLT.open_f.C0, "SOLT/Open/Param/C0_Female", 0.0}, + {&SOLT.open_f.C1, "SOLT/Open/Param/C1_Female", 0.0}, + {&SOLT.open_f.C2, "SOLT/Open/Param/C2_Female", 0.0}, + {&SOLT.open_f.C3, "SOLT/Open/Param/C3_Female", 0.0}, + {&SOLT.open_f.useMeasurements, "SOLT/Open/Measurements/Use_Female", false}, + {&SOLT.open_f.file, "SOLT/Open/Measurements/File_Female", ""}, + {&SOLT.open_f.Sparam, "SOLT/Open/Measurements/Port_Female", 0}, - {&SOLT.Short.Z0, "SOLT/Short/Param/Z0", 50.0}, - {&SOLT.Short.delay, "SOLT/Short/Param/Delay", 0.0}, - {&SOLT.Short.loss, "SOLT/Short/Param/Loss", 0.0}, - {&SOLT.Short.L0, "SOLT/Short/Param/L0", 0.0}, - {&SOLT.Short.L1, "SOLT/Short/Param/L1", 0.0}, - {&SOLT.Short.L2, "SOLT/Short/Param/L2", 0.0}, - {&SOLT.Short.L3, "SOLT/Short/Param/L3", 0.0}, - {&SOLT.Short.useMeasurements, "SOLT/Short/Measurements/Use", false}, - {&SOLT.Short.file, "SOLT/Short/Measurements/File", ""}, - {&SOLT.Short.Sparam, "SOLT/Short/Measurements/Port", 0}, + {&SOLT.short_m.Z0, "SOLT/Short/Param/Z0", 50.0}, + {&SOLT.short_m.delay, "SOLT/Short/Param/Delay", 0.0}, + {&SOLT.short_m.loss, "SOLT/Short/Param/Loss", 0.0}, + {&SOLT.short_m.L0, "SOLT/Short/Param/L0", 0.0}, + {&SOLT.short_m.L1, "SOLT/Short/Param/L1", 0.0}, + {&SOLT.short_m.L2, "SOLT/Short/Param/L2", 0.0}, + {&SOLT.short_m.L3, "SOLT/Short/Param/L3", 0.0}, + {&SOLT.short_m.useMeasurements, "SOLT/Short/Measurements/Use", false}, + {&SOLT.short_m.file, "SOLT/Short/Measurements/File", ""}, + {&SOLT.short_m.Sparam, "SOLT/Short/Measurements/Port", 0}, + {&SOLT.short_f.Z0, "SOLT/Short/Param/Z0_Female", 50.0}, + {&SOLT.short_f.delay, "SOLT/Short/Param/Delay_Female", 0.0}, + {&SOLT.short_f.loss, "SOLT/Short/Param/Loss_Female", 0.0}, + {&SOLT.short_f.L0, "SOLT/Short/Param/L0_Female", 0.0}, + {&SOLT.short_f.L1, "SOLT/Short/Param/L1_Female", 0.0}, + {&SOLT.short_f.L2, "SOLT/Short/Param/L2_Female", 0.0}, + {&SOLT.short_f.L3, "SOLT/Short/Param/L3_Female", 0.0}, + {&SOLT.short_f.useMeasurements, "SOLT/Short/Measurements/Use_Female", false}, + {&SOLT.short_f.file, "SOLT/Short/Measurements/File_Female", ""}, + {&SOLT.short_f.Sparam, "SOLT/Short/Measurements/Port_Female", 0}, - {&SOLT.Load.Z0, "SOLT/Load/Param/Z0", 50.0}, - {&SOLT.Load.delay, "SOLT/Load/Param/Delay", 0.0}, - {&SOLT.Load.Cparallel, "SOLT/Load/Param/C", 0.0}, - {&SOLT.Load.Lseries, "SOLT/Load/Param/L", 0.0}, - {&SOLT.Load.useMeasurements, "SOLT/Load/Measurements/Use", false}, - {&SOLT.Load.file, "SOLT/Load/Measurements/File", ""}, - {&SOLT.Load.Sparam, "SOLT/Load/Measurements/Port", 0}, + {&SOLT.load_m.Z0, "SOLT/Load/Param/Z0", 50.0}, + {&SOLT.load_m.delay, "SOLT/Load/Param/Delay", 0.0}, + {&SOLT.load_m.Cparallel, "SOLT/Load/Param/C", 0.0}, + {&SOLT.load_m.Lseries, "SOLT/Load/Param/L", 0.0}, + {&SOLT.load_m.useMeasurements, "SOLT/Load/Measurements/Use", false}, + {&SOLT.load_m.file, "SOLT/Load/Measurements/File", ""}, + {&SOLT.load_m.Sparam, "SOLT/Load/Measurements/Port", 0}, + {&SOLT.load_f.Z0, "SOLT/Load/Param/Z0_Female", 50.0}, + {&SOLT.load_f.delay, "SOLT/Load/Param/Delay_Female", 0.0}, + {&SOLT.load_f.Cparallel, "SOLT/Load/Param/C_Female", 0.0}, + {&SOLT.load_f.Lseries, "SOLT/Load/Param/L_Female", 0.0}, + {&SOLT.load_f.useMeasurements, "SOLT/Load/Measurements/Use_Female", false}, + {&SOLT.load_f.file, "SOLT/Load/Measurements/File_Female", ""}, + {&SOLT.load_f.Sparam, "SOLT/Load/Measurements/Port_Female", 0}, {&SOLT.Through.Z0, "SOLT/Through/Param/Z0", 50.0}, {&SOLT.Through.delay, "SOLT/Through/Param/Delay", 0.0}, @@ -143,6 +180,8 @@ private: {&SOLT.Through.Sparam1, "SOLT/Through/Measurements/Port1", 0}, {&SOLT.Through.Sparam2, "SOLT/Through/Measurements/Port2", 1}, + {&SOLT.separate_male_female, "SOLT/SeparateMaleFemale", false}, + {&TRL.Through.Z0, "TRL/Through/Z0", 50.0}, {&TRL.Reflection.isShort, "TRL/Reflect/isShort", false}, {&TRL.Line.delay, "TRL/Line/Delay", 74.0}, diff --git a/Software/PC_Application/Calibration/calkitdialog.cpp b/Software/PC_Application/Calibration/calkitdialog.cpp index 2687411..a76921b 100644 --- a/Software/PC_Application/Calibration/calkitdialog.cpp +++ b/Software/PC_Application/Calibration/calkitdialog.cpp @@ -47,6 +47,29 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : ui->load_parC->setPrefixes("fpnum "); ui->load_serL->setUnit("H"); ui->load_serL->setPrefixes("fpnum "); + + // Same setup for female standards + ui->OpenType_f->setId(ui->open_coefficients_f, 0); + ui->OpenType_f->setId(ui->open_measurement_f, 1); + + ui->ShortType_f->setId(ui->short_coefficients_f, 0); + ui->ShortType_f->setId(ui->short_measurement_f, 1); + + ui->LoadType_f->setId(ui->load_coefficients_f, 0); + ui->LoadType_f->setId(ui->load_measurement_f, 1); + + ui->open_touchstone_f->setPorts(1); + ui->short_touchstone_f->setPorts(1); + ui->load_touchstone_f->setPorts(1); + + ui->short_Z0_f->setUnit("Ω"); + ui->open_Z0_f->setUnit("Ω"); + ui->load_Z0_f->setUnit("Ω"); + ui->load_parC_f->setUnit("F"); + ui->load_parC_f->setPrefixes("fpnum "); + ui->load_serL_f->setUnit("H"); + ui->load_serL_f->setPrefixes("fpnum "); + ui->through_Z0->setUnit("Ω"); ui->TRL_through_Z0->setUnit("Ω"); @@ -59,6 +82,24 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : editKit.clearTouchstoneCache(); ownKit = editKit; + + connect(ui->cbStandardDefinition, qOverload(&QComboBox::currentIndexChanged), [=](int index){ + if (index == 0) { + // common definition, hide tab bars, set all to male tab + ui->mf_short->setCurrentIndex(0); + ui->mf_short->tabBar()->hide(); + ui->mf_open->setCurrentIndex(0); + ui->mf_open->tabBar()->hide(); + ui->mf_load->setCurrentIndex(0); + ui->mf_load->tabBar()->hide(); + } else { + // separate definitions for male/female standards + ui->mf_short->tabBar()->show(); + ui->mf_open->tabBar()->show(); + ui->mf_load->tabBar()->show(); + } + }); + updateEntries(); connect(ui->TRL_line_min, &SIUnitEdit::valueChanged, [=](double newval){ @@ -85,6 +126,15 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : if(ui->load_measurement->isChecked() && !ui->load_touchstone->getStatus()) { ok = false; } + if(ui->open_measurement_f->isChecked() && !ui->open_touchstone_f->getStatus()) { + ok = false; + } + if(ui->short_measurement_f->isChecked() && !ui->short_touchstone_f->getStatus()) { + ok = false; + } + if(ui->load_measurement_f->isChecked() && !ui->load_touchstone_f->getStatus()) { + ok = false; + } if(ui->through_measurement->isChecked() && !ui->through_touchstone->getStatus()) { ok = false; } @@ -95,6 +145,9 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : connect(ui->open_touchstone, &TouchstoneImport::statusChanged, UpdateStatus); connect(ui->short_touchstone, &TouchstoneImport::statusChanged, UpdateStatus); connect(ui->load_touchstone, &TouchstoneImport::statusChanged, UpdateStatus); + connect(ui->open_touchstone_f, &TouchstoneImport::statusChanged, UpdateStatus); + connect(ui->short_touchstone_f, &TouchstoneImport::statusChanged, UpdateStatus); + connect(ui->load_touchstone_f, &TouchstoneImport::statusChanged, UpdateStatus); connect(ui->through_touchstone, &TouchstoneImport::statusChanged, UpdateStatus); connect(ui->OpenType, qOverload(&QButtonGroup::buttonClicked), [=](int) { @@ -106,6 +159,15 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : connect(ui->LoadType, qOverload(&QButtonGroup::buttonClicked), [=](int) { UpdateStatus(); }); + connect(ui->OpenType_f, qOverload(&QButtonGroup::buttonClicked), [=](int) { + UpdateStatus(); + }); + connect(ui->ShortType_f, qOverload(&QButtonGroup::buttonClicked), [=](int) { + UpdateStatus(); + }); + connect(ui->LoadType_f, qOverload(&QButtonGroup::buttonClicked), [=](int) { + UpdateStatus(); + }); connect(ui->ThroughType, qOverload(&QButtonGroup::buttonClicked), [=](int) { UpdateStatus(); }); @@ -152,49 +214,83 @@ void CalkitDialog::parseEntries() ownKit.description = ui->description->toPlainText(); // type - ownKit.SOLT.Open.useMeasurements = ui->open_measurement->isChecked(); - ownKit.SOLT.Short.useMeasurements = ui->short_measurement->isChecked(); - ownKit.SOLT.Load.useMeasurements = ui->load_measurement->isChecked(); + ownKit.SOLT.open_m.useMeasurements = ui->open_measurement->isChecked(); + ownKit.SOLT.short_m.useMeasurements = ui->short_measurement->isChecked(); + ownKit.SOLT.load_m.useMeasurements = ui->load_measurement->isChecked(); + ownKit.SOLT.open_f.useMeasurements = ui->open_measurement_f->isChecked(); + ownKit.SOLT.short_f.useMeasurements = ui->short_measurement_f->isChecked(); + ownKit.SOLT.load_f.useMeasurements = ui->load_measurement_f->isChecked(); ownKit.SOLT.Through.useMeasurements = ui->through_measurement->isChecked(); // coefficients - ownKit.SOLT.Open.Z0 = ui->open_Z0->value(); - ownKit.SOLT.Open.delay = ui->open_delay->value(); - ownKit.SOLT.Open.loss = ui->open_loss->value(); - ownKit.SOLT.Open.C0 = ui->open_C0->value(); - ownKit.SOLT.Open.C1 = ui->open_C1->value(); - ownKit.SOLT.Open.C2 = ui->open_C2->value(); - ownKit.SOLT.Open.C3 = ui->open_C3->value(); + ownKit.SOLT.open_m.Z0 = ui->open_Z0->value(); + ownKit.SOLT.open_m.delay = ui->open_delay->value(); + ownKit.SOLT.open_m.loss = ui->open_loss->value(); + ownKit.SOLT.open_m.C0 = ui->open_C0->value(); + ownKit.SOLT.open_m.C1 = ui->open_C1->value(); + ownKit.SOLT.open_m.C2 = ui->open_C2->value(); + ownKit.SOLT.open_m.C3 = ui->open_C3->value(); - ownKit.SOLT.Short.Z0 = ui->short_Z0->value(); - ownKit.SOLT.Short.delay = ui->short_delay->value(); - ownKit.SOLT.Short.loss = ui->short_loss->value(); - ownKit.SOLT.Short.L0 = ui->short_L0->value(); - ownKit.SOLT.Short.L1 = ui->short_L1->value(); - ownKit.SOLT.Short.L2 = ui->short_L2->value(); - ownKit.SOLT.Short.L3 = ui->short_L3->value(); + ownKit.SOLT.short_m.Z0 = ui->short_Z0->value(); + ownKit.SOLT.short_m.delay = ui->short_delay->value(); + ownKit.SOLT.short_m.loss = ui->short_loss->value(); + ownKit.SOLT.short_m.L0 = ui->short_L0->value(); + ownKit.SOLT.short_m.L1 = ui->short_L1->value(); + ownKit.SOLT.short_m.L2 = ui->short_L2->value(); + ownKit.SOLT.short_m.L3 = ui->short_L3->value(); - ownKit.SOLT.Load.Z0 = ui->load_Z0->value(); - ownKit.SOLT.Load.delay = ui->load_delay->value(); - ownKit.SOLT.Load.Cparallel = ui->load_parC->value(); - ownKit.SOLT.Load.Lseries = ui->load_serL->value(); + ownKit.SOLT.load_m.Z0 = ui->load_Z0->value(); + ownKit.SOLT.load_m.delay = ui->load_delay->value(); + ownKit.SOLT.load_m.Cparallel = ui->load_parC->value(); + ownKit.SOLT.load_m.Lseries = ui->load_serL->value(); + + ownKit.SOLT.open_f.Z0 = ui->open_Z0_f->value(); + ownKit.SOLT.open_f.delay = ui->open_delay_f->value(); + ownKit.SOLT.open_f.loss = ui->open_loss_f->value(); + ownKit.SOLT.open_f.C0 = ui->open_C0_f->value(); + ownKit.SOLT.open_f.C1 = ui->open_C1_f->value(); + ownKit.SOLT.open_f.C2 = ui->open_C2_f->value(); + ownKit.SOLT.open_f.C3 = ui->open_C3_f->value(); + + ownKit.SOLT.short_f.Z0 = ui->short_Z0_f->value(); + ownKit.SOLT.short_f.delay = ui->short_delay_f->value(); + ownKit.SOLT.short_f.loss = ui->short_loss_f->value(); + ownKit.SOLT.short_f.L0 = ui->short_L0_f->value(); + ownKit.SOLT.short_f.L1 = ui->short_L1_f->value(); + ownKit.SOLT.short_f.L2 = ui->short_L2_f->value(); + ownKit.SOLT.short_f.L3 = ui->short_L3_f->value(); + + ownKit.SOLT.load_f.Z0 = ui->load_Z0_f->value(); + ownKit.SOLT.load_f.delay = ui->load_delay_f->value(); + ownKit.SOLT.load_f.Cparallel = ui->load_parC_f->value(); + ownKit.SOLT.load_f.Lseries = ui->load_serL_f->value(); ownKit.SOLT.Through.Z0 = ui->through_Z0->value(); ownKit.SOLT.Through.delay = ui->through_delay->value(); ownKit.SOLT.Through.loss = ui->through_loss->value(); + ownKit.SOLT.separate_male_female = ui->cbStandardDefinition->currentIndex() == 1; + // file - ownKit.SOLT.Open.file = ui->open_touchstone->getFilename(); - ownKit.SOLT.Short.file = ui->short_touchstone->getFilename(); - ownKit.SOLT.Load.file = ui->load_touchstone->getFilename(); + ownKit.SOLT.open_m.file = ui->open_touchstone->getFilename(); + ownKit.SOLT.short_m.file = ui->short_touchstone->getFilename(); + ownKit.SOLT.load_m.file = ui->load_touchstone->getFilename(); ownKit.SOLT.Through.file = ui->through_touchstone->getFilename(); - ownKit.SOLT.Open.Sparam = ui->open_touchstone->getPorts()[0]; - ownKit.SOLT.Short.Sparam = ui->short_touchstone->getPorts()[0]; - ownKit.SOLT.Load.Sparam = ui->load_touchstone->getPorts()[0]; + ownKit.SOLT.open_m.Sparam = ui->open_touchstone->getPorts()[0]; + ownKit.SOLT.short_m.Sparam = ui->short_touchstone->getPorts()[0]; + ownKit.SOLT.load_m.Sparam = ui->load_touchstone->getPorts()[0]; ownKit.SOLT.Through.Sparam1 = ui->through_touchstone->getPorts()[0]; ownKit.SOLT.Through.Sparam2 = ui->through_touchstone->getPorts()[1]; + ownKit.SOLT.open_f.file = ui->open_touchstone_f->getFilename(); + ownKit.SOLT.short_f.file = ui->short_touchstone_f->getFilename(); + ownKit.SOLT.load_f.file = ui->load_touchstone_f->getFilename(); + + ownKit.SOLT.open_f.Sparam = ui->open_touchstone_f->getPorts()[0]; + ownKit.SOLT.short_f.Sparam = ui->short_touchstone_f->getPorts()[0]; + ownKit.SOLT.load_f.Sparam = ui->load_touchstone_f->getPorts()[0]; + // TRL ownKit.TRL.Through.Z0 = ui->TRL_through_Z0->value(); ownKit.TRL.Reflection.isShort = ui->TRL_R_short->isChecked(); @@ -212,70 +308,124 @@ void CalkitDialog::updateEntries() ui->description->setPlainText(ownKit.description); // Coefficients - ui->open_Z0->setValueQuiet(ownKit.SOLT.Open.Z0); - ui->open_delay->setValueQuiet(ownKit.SOLT.Open.delay); - ui->open_loss->setValueQuiet(ownKit.SOLT.Open.loss); - ui->open_C0->setValueQuiet(ownKit.SOLT.Open.C0); - ui->open_C1->setValueQuiet(ownKit.SOLT.Open.C1); - ui->open_C2->setValueQuiet(ownKit.SOLT.Open.C2); - ui->open_C3->setValueQuiet(ownKit.SOLT.Open.C3); + ui->open_Z0->setValueQuiet(ownKit.SOLT.open_m.Z0); + ui->open_delay->setValueQuiet(ownKit.SOLT.open_m.delay); + ui->open_loss->setValueQuiet(ownKit.SOLT.open_m.loss); + ui->open_C0->setValueQuiet(ownKit.SOLT.open_m.C0); + ui->open_C1->setValueQuiet(ownKit.SOLT.open_m.C1); + ui->open_C2->setValueQuiet(ownKit.SOLT.open_m.C2); + ui->open_C3->setValueQuiet(ownKit.SOLT.open_m.C3); - ui->short_Z0->setValueQuiet(ownKit.SOLT.Short.Z0); - ui->short_delay->setValueQuiet(ownKit.SOLT.Short.delay); - ui->short_loss->setValueQuiet(ownKit.SOLT.Short.loss); - ui->short_L0->setValueQuiet(ownKit.SOLT.Short.L0); - ui->short_L1->setValueQuiet(ownKit.SOLT.Short.L1); - ui->short_L2->setValueQuiet(ownKit.SOLT.Short.L2); - ui->short_L3->setValueQuiet(ownKit.SOLT.Short.L3); + ui->short_Z0->setValueQuiet(ownKit.SOLT.short_m.Z0); + ui->short_delay->setValueQuiet(ownKit.SOLT.short_m.delay); + ui->short_loss->setValueQuiet(ownKit.SOLT.short_m.loss); + ui->short_L0->setValueQuiet(ownKit.SOLT.short_m.L0); + ui->short_L1->setValueQuiet(ownKit.SOLT.short_m.L1); + ui->short_L2->setValueQuiet(ownKit.SOLT.short_m.L2); + ui->short_L3->setValueQuiet(ownKit.SOLT.short_m.L3); - ui->load_Z0->setValueQuiet(ownKit.SOLT.Load.Z0); - ui->load_delay->setValueQuiet(ownKit.SOLT.Load.delay); - ui->load_parC->setValueQuiet(ownKit.SOLT.Load.Cparallel); - ui->load_serL->setValueQuiet(ownKit.SOLT.Load.Lseries); + ui->load_Z0->setValueQuiet(ownKit.SOLT.load_m.Z0); + ui->load_delay->setValueQuiet(ownKit.SOLT.load_m.delay); + ui->load_parC->setValueQuiet(ownKit.SOLT.load_m.Cparallel); + ui->load_serL->setValueQuiet(ownKit.SOLT.load_m.Lseries); + + ui->open_Z0_f->setValueQuiet(ownKit.SOLT.open_f.Z0); + ui->open_delay_f->setValueQuiet(ownKit.SOLT.open_f.delay); + ui->open_loss_f->setValueQuiet(ownKit.SOLT.open_f.loss); + ui->open_C0_f->setValueQuiet(ownKit.SOLT.open_f.C0); + ui->open_C1_f->setValueQuiet(ownKit.SOLT.open_f.C1); + ui->open_C2_f->setValueQuiet(ownKit.SOLT.open_f.C2); + ui->open_C3_f->setValueQuiet(ownKit.SOLT.open_f.C3); + + ui->short_Z0_f->setValueQuiet(ownKit.SOLT.short_f.Z0); + ui->short_delay_f->setValueQuiet(ownKit.SOLT.short_f.delay); + ui->short_loss_f->setValueQuiet(ownKit.SOLT.short_f.loss); + ui->short_L0_f->setValueQuiet(ownKit.SOLT.short_f.L0); + ui->short_L1_f->setValueQuiet(ownKit.SOLT.short_f.L1); + ui->short_L2_f->setValueQuiet(ownKit.SOLT.short_f.L2); + ui->short_L3_f->setValueQuiet(ownKit.SOLT.short_f.L3); + + ui->load_Z0_f->setValueQuiet(ownKit.SOLT.load_f.Z0); + ui->load_delay_f->setValueQuiet(ownKit.SOLT.load_f.delay); + ui->load_parC_f->setValueQuiet(ownKit.SOLT.load_f.Cparallel); + ui->load_serL_f->setValueQuiet(ownKit.SOLT.load_f.Lseries); ui->through_Z0->setValueQuiet(ownKit.SOLT.Through.Z0); ui->through_delay->setValueQuiet(ownKit.SOLT.Through.delay); ui->through_loss->setValueQuiet(ownKit.SOLT.Through.loss); // Measurements - ui->open_touchstone->setFile(ownKit.SOLT.Open.file); - ui->open_touchstone->selectPort(0, ownKit.SOLT.Open.Sparam); + ui->open_touchstone->setFile(ownKit.SOLT.open_m.file); + ui->open_touchstone->selectPort(0, ownKit.SOLT.open_m.Sparam); - ui->short_touchstone->setFile(ownKit.SOLT.Short.file); - ui->short_touchstone->selectPort(0, ownKit.SOLT.Short.Sparam); + ui->short_touchstone->setFile(ownKit.SOLT.short_m.file); + ui->short_touchstone->selectPort(0, ownKit.SOLT.short_m.Sparam); - ui->load_touchstone->setFile(ownKit.SOLT.Load.file); - ui->load_touchstone->selectPort(0, ownKit.SOLT.Load.Sparam); + ui->load_touchstone->setFile(ownKit.SOLT.load_m.file); + ui->load_touchstone->selectPort(0, ownKit.SOLT.load_m.Sparam); + + ui->open_touchstone_f->setFile(ownKit.SOLT.open_f.file); + ui->open_touchstone_f->selectPort(0, ownKit.SOLT.open_f.Sparam); + + ui->short_touchstone_f->setFile(ownKit.SOLT.short_f.file); + ui->short_touchstone_f->selectPort(0, ownKit.SOLT.short_f.Sparam); + + ui->load_touchstone_f->setFile(ownKit.SOLT.load_f.file); + ui->load_touchstone_f->selectPort(0, ownKit.SOLT.load_f.Sparam); ui->through_touchstone->setFile(ownKit.SOLT.Through.file); ui->through_touchstone->selectPort(0, ownKit.SOLT.Through.Sparam1); ui->through_touchstone->selectPort(1, ownKit.SOLT.Through.Sparam2); // Type - if (ownKit.SOLT.Open.useMeasurements) { + if (ownKit.SOLT.open_m.useMeasurements) { ui->open_measurement->click(); } else { ui->open_coefficients->click(); } - if (ownKit.SOLT.Short.useMeasurements) { + if (ownKit.SOLT.short_m.useMeasurements) { ui->short_measurement->click(); } else { ui->short_coefficients->click(); } - if (ownKit.SOLT.Load.useMeasurements) { + if (ownKit.SOLT.load_m.useMeasurements) { ui->load_measurement->click(); } else { ui->load_coefficients->click(); } + if (ownKit.SOLT.open_f.useMeasurements) { + ui->open_measurement_f->click(); + } else { + ui->open_coefficients_f->click(); + } + + if (ownKit.SOLT.short_f.useMeasurements) { + ui->short_measurement_f->click(); + } else { + ui->short_coefficients_f->click(); + } + + if (ownKit.SOLT.load_f.useMeasurements) { + ui->load_measurement_f->click(); + } else { + ui->load_coefficients_f->click(); + } + if (ownKit.SOLT.Through.useMeasurements) { ui->through_measurement->click(); } else { ui->through_coefficients->click(); } + if (ownKit.SOLT.separate_male_female) { + ui->cbStandardDefinition->setCurrentIndex(1); + } else { + ui->cbStandardDefinition->setCurrentIndex(0); + } + // TRL ui->TRL_through_Z0->setValueQuiet(ownKit.TRL.Through.Z0); if(ownKit.TRL.Reflection.isShort) { diff --git a/Software/PC_Application/Calibration/calkitdialog.h b/Software/PC_Application/Calibration/calkitdialog.h index b06e073..a8b02b5 100644 --- a/Software/PC_Application/Calibration/calkitdialog.h +++ b/Software/PC_Application/Calibration/calkitdialog.h @@ -20,7 +20,7 @@ public: explicit CalkitDialog(Calkit &c, QWidget *parent = nullptr); ~CalkitDialog(); -private: +private: void parseEntries(); void updateEntries(); Ui::CalkitDialog *ui; diff --git a/Software/PC_Application/Calibration/calkitdialog.ui b/Software/PC_Application/Calibration/calkitdialog.ui index b904fea..b6b29fc 100644 --- a/Software/PC_Application/Calibration/calkitdialog.ui +++ b/Software/PC_Application/Calibration/calkitdialog.ui @@ -9,8 +9,8 @@ 0 0 - 1141 - 602 + 1213 + 626 @@ -28,7 +28,7 @@ false - + @@ -93,512 +93,945 @@ SOLT - + - + - - - - 16 - - - - Short - - - Qt::AlignCenter - - - - - + - - - Coefficients - - - ShortType - - - - - - - Measurement file - - - ShortType - - - - - - - - - 0 - - - + - - - + + + Standard definition: + + + + + + + 1 + + + + Use one common definition for open/short/load standard + + + + + Use separate male/female definitions for open/short/load standard + + + + + + + + + + + + + + + 16 + + - Offset delay [ps]: + Short + + + Qt::AlignCenter - - + + + + 0 + + + + Male + + + + + + + + Coefficients + + + ShortType + + + + + + + Measurement file + + + ShortType + + + + + + + + + 0 + + + + + + + + + Offset delay [ps]: + + + + + + + + + + Offset loss [GΩ/s]: + + + + + + + + + + <html><head/><body><p>L0 [10<span style=" vertical-align:super;">-12</span>H]:</p></body></html> + + + + + + + + + + <html><head/><body><p>L1 [10<span style=" vertical-align:super;">-24</span>H/Hz]:</p></body></html> + + + + + + + + + + <html><head/><body><p>L2 [10<span style=" vertical-align:super;">-33</span>H/Hz<span style=" vertical-align:super;">2</span>]:</p></body></html> + + + + + + + + + + <html><head/><body><p>L3 [10<span style=" vertical-align:super;">-42</span>H/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html> + + + + + + + + + + true + + + + + + + Z0: + + + + + + + + + + + + + + + + + + + + + Female + + + + + + + + Coefficients + + + ShortType_f + + + + + + + Measurement file + + + ShortType_f + + + + + + + + + 0 + + + + + + + + + Offset delay [ps]: + + + + + + + + + + Offset loss [GΩ/s]: + + + + + + + + + + <html><head/><body><p>L0 [10<span style=" vertical-align:super;">-12</span>H]:</p></body></html> + + + + + + + + + + <html><head/><body><p>L1 [10<span style=" vertical-align:super;">-24</span>H/Hz]:</p></body></html> + + + + + + + + + + <html><head/><body><p>L2 [10<span style=" vertical-align:super;">-33</span>H/Hz<span style=" vertical-align:super;">2</span>]:</p></body></html> + + + + + + + + + + <html><head/><body><p>L3 [10<span style=" vertical-align:super;">-42</span>H/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html> + + + + + + + + + + true + + + + + + + Z0: + + + + + + + + + + + + + + + + + + + - - + + + + + + Qt::Vertical + + + + + + + + + + 16 + + - Offset loss [GΩ/s]: + Open + + + Qt::AlignCenter - - + + + + 0 + + + + Male + + + + + + + + Coefficients + + + OpenType + + + + + + + Measurement file + + + OpenType + + + + + + + + + 0 + + + + + + + + + Offset delay [ps]: + + + + + + + + + + Offset loss [GΩ/s]: + + + + + + + + + + C0 [10<sup>-15</sup>F]: + + + + + + + + + + C1 [10<sup>-27</sup>F/Hz]: + + + + + + + + + + C2 [10<sup>-36</sup>F/Hz<sup>2</sup>]: + + + + + + + + + + <html><head/><body><p>C3 [10<span style=" vertical-align:super;">-45</span>F/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html> + + + + + + + + + + true + + + + + + + Z0: + + + + + + + + + + + + + + + + + + + + + Female + + + + + + + + Coefficients + + + OpenType_f + + + + + + + Measurement file + + + OpenType_f + + + + + + + + + 0 + + + + + + + + + Offset delay [ps]: + + + + + + + + + + Offset loss [GΩ/s]: + + + + + + + + + + C0 [10<sup>-15</sup>F]: + + + + + + + + + + C1 [10<sup>-27</sup>F/Hz]: + + + + + + + + + + C2 [10<sup>-36</sup>F/Hz<sup>2</sup>]: + + + + + + + + + + <html><head/><body><p>C3 [10<span style=" vertical-align:super;">-45</span>F/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html> + + + + + + + + + + true + + + + + + + Z0: + + + + + + + + + + + + + + + + + + + - - + + + + + + Qt::Vertical + + + + + + + + + + 16 + + - <html><head/><body><p>L0 [10<span style=" vertical-align:super;">-12</span>H]:</p></body></html> + Load + + + Qt::AlignCenter - - - - - - - <html><head/><body><p>L1 [10<span style=" vertical-align:super;">-24</span>H/Hz]:</p></body></html> - - - - - - - - - - <html><head/><body><p>L2 [10<span style=" vertical-align:super;">-33</span>H/Hz<span style=" vertical-align:super;">2</span>]:</p></body></html> - - - - - - - - - - <html><head/><body><p>L3 [10<span style=" vertical-align:super;">-42</span>H/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html> - - - - - - - - - - true - - - - - - - Z0: + + + + 0 + + + Male + + + + + + + + Coefficients + + + LoadType + + + + + + + Measurement file + + + LoadType + + + + + + + + + 0 + + + + + + + + + Z0: + + + + + + + true + + + + + + + Offset delay [ps]: + + + + + + + + + + Parallel C: + + + + + + + + + + Series L: + + + + + + + + + + + + + + + + + + + + + + + + Female + + + + + + + + Coefficients + + + LoadType_f + + + + + + + Measurement file + + + LoadType_f + + + + + + + + + 0 + + + + + + + + + Z0: + + + + + + + true + + + + + + + Offset delay [ps]: + + + + + + + + + + Parallel C: + + + + + + + + + + Series L: + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - Qt::Vertical - - - - - - - - - - 16 - - - - Open - - - Qt::AlignCenter - - - - - - - - - Coefficients - - - OpenType - - - - - - - Measurement file - - - OpenType - - - - - 0 - - - - - - - - - Offset delay [ps]: - - - - - - - - - - Offset loss [GΩ/s]: - - - - - - - - - - C0 [10<sup>-15</sup>F]: - - - - - - - - - - C1 [10<sup>-27</sup>F/Hz]: - - - - - - - - - - C2 [10<sup>-36</sup>F/Hz<sup>2</sup>]: - - - - - - - - - - <html><head/><body><p>C3 [10<span style=" vertical-align:super;">-45</span>F/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html> - - - - - - - - - - true - - - - - - - Z0: - - - - - - - - - - - - - - - - - - - - - - Qt::Vertical - - - - - - - - - - 16 - - - - Load - - - Qt::AlignCenter + + + Qt::Vertical - + - - - Coefficients + + + + 16 + + + + Through + + + Qt::AlignCenter - - LoadType - - - - Measurement file + + + + + Coefficients + + + ThroughType + + + + + + + Measurement file + + + ThroughType + + + + + + + + + 0 - - LoadType - + + + + + + + + Z0: + + + + + + + false + + + + + + + Delay [ps]: + + + + + + + + + + Loss [GΩ/s]: + + + + + + + + + + + + + + + + + - - - - 0 - - - - - - - - - Z0: - - - - - - - true - - - - - - - Offset delay [ps]: - - - - - - - - - - Parallel C: - - - - - - - Series L: - - - - - - - - - - - - - - - - - - - - - - - - - - - - Qt::Vertical - - - - - - - - - - 16 - - - - Through - - - Qt::AlignCenter - - - - - - - - - Coefficients - - - ThroughType - - - - - - - Measurement file - - - ThroughType - - - - - - - - - 0 - - - - - - - - - Z0: - - - - - - - false - - - - - - - Delay [ps]: - - - - - - - - - - Loss [GΩ/s]: - - - - - - - - - - - - - - - - - - - @@ -607,7 +1040,7 @@ TRL - + @@ -867,19 +1300,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -986,12 +1406,63 @@ + + ShortType_f + buttonClicked(int) + short_stack_f + setCurrentIndex(int) + + + -1 + -1 + + + 165 + 442 + + + + + OpenType_f + buttonClicked(int) + open_stack_f + setCurrentIndex(int) + + + -1 + -1 + + + 466 + 442 + + + + + LoadType_f + buttonClicked(int) + load_stack_f + setCurrentIndex(int) + + + -1 + -1 + + + 767 + 442 + + + - - - + + + + + + diff --git a/Software/PC_Application/Calibration/measurementmodel.cpp b/Software/PC_Application/Calibration/measurementmodel.cpp index 4b17b4c..0f382b2 100644 --- a/Software/PC_Application/Calibration/measurementmodel.cpp +++ b/Software/PC_Application/Calibration/measurementmodel.cpp @@ -19,21 +19,46 @@ int MeasurementModel::rowCount(const QModelIndex &) const int MeasurementModel::columnCount(const QModelIndex &) const { - return ColIndexLast; + return (int) ColIndex::Last; } QVariant MeasurementModel::data(const QModelIndex &index, int role) const { auto info = cal->getMeasurementInfo(measurements[index.row()]); if(role == Qt::DisplayRole) { - switch(index.column()) { - case ColIndexName: + switch((ColIndex) index.column()) { + case ColIndex::Name: return info.name; break; - case ColIndexDescription: + case ColIndex::Gender: + switch(measurements[index.row()]) { + case Calibration::Measurement::Port1Load: + case Calibration::Measurement::Port1Open: + case Calibration::Measurement::Port1Short: + if(cal->getPortStandard(1) == Calibration::PortStandard::Male) { + return "Male"; + } else { + return "Female"; + } + break; + case Calibration::Measurement::Port2Load: + case Calibration::Measurement::Port2Open: + case Calibration::Measurement::Port2Short: + if(cal->getPortStandard(2) == Calibration::PortStandard::Male) { + return "Male"; + } else { + return "Female"; + } + break; + default: + return ""; + } + + break; + case ColIndex::Description: return info.prerequisites; break; - case ColIndexData: + case ColIndex::Data: if(info.points > 0) { QString data = QString::number(info.points); data.append(" points from "); @@ -45,16 +70,17 @@ QVariant MeasurementModel::data(const QModelIndex &index, int role) const return "Not available"; } break; - case ColIndexDate: + case ColIndex::Date: return info.timestamp.toString("dd.MM.yyyy hh:mm:ss"); break; } } else if(role == Qt::SizeHintRole) { - switch(index.column()) { - case ColIndexName: return 200; break; - case ColIndexDescription: return 500; break; - case ColIndexData: return 300; break; - case ColIndexDate: return 300; break; + switch((ColIndex) index.column()) { + case ColIndex::Name: return 200; break; + case ColIndex::Gender: return 150; break; + case ColIndex::Description: return 500; break; + case ColIndex::Data: return 300; break; + case ColIndex::Date: return 300; break; default: return QVariant(); break; } } @@ -65,11 +91,12 @@ QVariant MeasurementModel::data(const QModelIndex &index, int role) const QVariant MeasurementModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch(section) { - case ColIndexName: return "Type"; break; - case ColIndexDescription: return "Prerequisites"; break; - case ColIndexData: return "Statistics"; break; - case ColIndexDate: return "Timestamp"; break; + switch((ColIndex) section) { + case ColIndex::Name: return "Type"; break; + case ColIndex::Gender: return "Gender"; break; + case ColIndex::Description: return "Prerequisites"; break; + case ColIndex::Data: return "Statistics"; break; + case ColIndex::Date: return "Timestamp"; break; default: return QVariant(); break; } } else { @@ -83,6 +110,11 @@ void MeasurementModel::measurementUpdated(Calibration::Measurement m) auto it = std::find(measurements.begin(), measurements.end(), m); if(it != measurements.end()) { int row = it - measurements.begin(); - emit dataChanged(index(row, 0), index(row, ColIndexLast - 1)); + emit dataChanged(index(row, 0), index(row, (int) ColIndex::Last - 1)); } } + +void MeasurementModel::genderUpdated() +{ + emit dataChanged(index(0, (int) ColIndex::Gender), index(rowCount() - 1, (int) ColIndex::Gender)); +} diff --git a/Software/PC_Application/Calibration/measurementmodel.h b/Software/PC_Application/Calibration/measurementmodel.h index 6674c27..edbba05 100644 --- a/Software/PC_Application/Calibration/measurementmodel.h +++ b/Software/PC_Application/Calibration/measurementmodel.h @@ -11,6 +11,15 @@ class MeasurementModel : public QAbstractTableModel { Q_OBJECT public: + enum class ColIndex { + Name, + Gender, + Description, + Data, + Date, + Last + }; + MeasurementModel(Calibration *cal, std::vector measurements); int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -20,15 +29,9 @@ public: public slots: void measurementUpdated(Calibration::Measurement m); + void genderUpdated(); private: - enum { - ColIndexName, - ColIndexDescription, - ColIndexData, - ColIndexDate, - ColIndexLast - }; Calibration *cal; std::vector measurements; }; diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index 702eb76..cb38875 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -218,6 +218,7 @@ SOURCES += \ Traces/tracewidget.cpp \ Traces/tracexyplot.cpp \ Traces/xyplotaxisdialog.cpp \ + Util/util.cpp \ VNA/Deembedding/deembedding.cpp \ VNA/Deembedding/deembeddingdialog.cpp \ VNA/Deembedding/deembeddingoption.cpp \ @@ -298,10 +299,11 @@ FORMS += \ DISTFILES += RESOURCES += \ - icons.qrc + icons.qrc \ + resources/librevna.qrc CONFIG += c++17 REVISION = $$system(git rev-parse HEAD) DEFINES += GITHASH=\\"\"$$REVISION\\"\" -DEFINES += FW_MAJOR=1 FW_MINOR=2 FW_PATCH=0 FW_SUFFIX=""#\\"\"-alpha.2\\"\" +DEFINES += FW_MAJOR=1 FW_MINOR=2 FW_PATCH=1 FW_SUFFIX=""#\\"\"-alpha.2\\"\" DEFINES -= _UNICODE UNICODE diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index 5552ab8..f585482 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -265,6 +265,13 @@ SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window) // Set initial sweep settings auto pref = Preferences::getInstance(); + + if(pref.Acquisition.useMedianAveraging) { + average.setMode(Averaging::Mode::Median); + } else { + average.setMode(Averaging::Mode::Mean); + } + if(pref.Startup.RememberSweepSettings) { LoadSweepSettings(); } else { @@ -315,7 +322,7 @@ nlohmann::json SpectrumAnalyzer::toJSON() tracking["enabled"] = settings.trackingGenerator ? true : false; tracking["port"] = settings.trackingGeneratorPort ? 2 : 1; tracking["offset"] = settings.trackingGeneratorOffset; - tracking["power"] = settings.trackingPower; + tracking["power"] = (double) settings.trackingPower / 100.0; // convert to dBm sweep["trackingGenerator"] = tracking; if(normalize.active) { @@ -413,6 +420,11 @@ using namespace std; void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) { + if(d.pointNum >= settings.pointNum) { + qWarning() << "Ignoring point with too large point number (" << d.pointNum << ")"; + return; + } + d = average.process(d); if(normalize.measuring) { @@ -451,6 +463,11 @@ void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) UpdateAverageCount(); markerModel->updateMarkers(); } + static unsigned int lastPoint = 0; + if(d.pointNum > 0 && d.pointNum != lastPoint + 1) { + qWarning() << "Got point" << d.pointNum << "but last received point was" << lastPoint << "("<<(d.pointNum-lastPoint-1)<<"missed points)"; + } + lastPoint = d.pointNum; } void SpectrumAnalyzer::SettingsChanged() @@ -1065,6 +1082,11 @@ void SpectrumAnalyzer::updateGraphColors() emit graphColorsChanged(); } +void SpectrumAnalyzer::setAveragingMode(Averaging::Mode mode) +{ + average.setMode(mode); +} + QString SpectrumAnalyzer::WindowToString(SpectrumAnalyzer::Window w) { switch(w) { diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h index 8d2c7f9..a4eacca 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h @@ -26,6 +26,7 @@ public: virtual void fromJSON(nlohmann::json j) override; void updateGraphColors(); + void setAveragingMode(Averaging::Mode mode); private: diff --git a/Software/PC_Application/Traces/Marker/marker.cpp b/Software/PC_Application/Traces/Marker/marker.cpp index c39f4b7..6ca8730 100644 --- a/Software/PC_Application/Traces/Marker/marker.cpp +++ b/Software/PC_Application/Traces/Marker/marker.cpp @@ -674,7 +674,7 @@ void Marker::updateContextmenu() auto typemenu = contextmenu.addMenu("Type"); auto typegroup = new QActionGroup(&contextmenu); for(auto t : getSupportedTypes()) { - auto setTypeAction = new QAction(typeToString(t)); + auto setTypeAction = new QAction(typeToString(t), typemenu); setTypeAction->setCheckable(true); if(t == type) { setTypeAction->setChecked(true); @@ -689,7 +689,7 @@ void Marker::updateContextmenu() auto table = contextmenu.addMenu("Data Format in Table"); auto tablegroup = new QActionGroup(&contextmenu); for(auto f : applicableFormats()) { - auto setFormatAction = new QAction(formatToString(f)); + auto setFormatAction = new QAction(formatToString(f), table); setFormatAction->setCheckable(true); if(f == formatTable) { setFormatAction->setChecked(true); @@ -703,7 +703,7 @@ void Marker::updateContextmenu() auto graph = contextmenu.addMenu("Show on Graph"); for(auto f : applicableFormats()) { - auto setFormatAction = new QAction(formatToString(f)); + auto setFormatAction = new QAction(formatToString(f), graph); setFormatAction->setCheckable(true); if(formatGraph.count(f)) { setFormatAction->setChecked(true); @@ -753,7 +753,7 @@ void Marker::updateContextmenu() } if(group != nullptr) { // "remove from group" available - auto removeGroup = new QAction("Remove from linked group"); + auto removeGroup = new QAction("Remove from linked group", &contextmenu); connect(removeGroup, &QAction::triggered, [=](){ group->remove(this); }); @@ -765,7 +765,7 @@ void Marker::updateContextmenu() } - auto deleteAction = new QAction("Delete"); + auto deleteAction = new QAction("Delete", &contextmenu); connect(deleteAction, &QAction::triggered, this, &Marker::deleteLater); contextmenu.addAction(deleteAction); } @@ -1560,6 +1560,9 @@ bool Marker::isMovable() // helper traces are never movable by the user return false; } + if(!parentTrace || parentTrace->size() == 0) { + return false; + } switch(type) { case Type::Manual: case Type::Delta: diff --git a/Software/PC_Application/Traces/Marker/markergroup.cpp b/Software/PC_Application/Traces/Marker/markergroup.cpp index 8be2695..7df0baa 100644 --- a/Software/PC_Application/Traces/Marker/markergroup.cpp +++ b/Software/PC_Application/Traces/Marker/markergroup.cpp @@ -22,9 +22,7 @@ bool MarkerGroup::add(Marker *m) connect(m, &Marker::positionChanged, this, &MarkerGroup::markerMoved); connect(m, &Marker::typeChanged, this, &MarkerGroup::checkMarker); connect(m, &Marker::domainChanged, this, &MarkerGroup::checkMarker); - connect(m, &Marker::deleted, [=](){ - remove(m); - }); + connect(m, &Marker::deleted, this, &MarkerGroup::remove); if(markers.size() > 0) { m->setPosition((*markers.begin())->getPosition()); diff --git a/Software/PC_Application/Traces/Marker/markergroup.h b/Software/PC_Application/Traces/Marker/markergroup.h index 4883573..5b7dc8c 100644 --- a/Software/PC_Application/Traces/Marker/markergroup.h +++ b/Software/PC_Application/Traces/Marker/markergroup.h @@ -17,7 +17,6 @@ public: ~MarkerGroup(); bool add(Marker *m); - bool remove(Marker *m); unsigned int getNumber() const; bool applicable(Marker *m); @@ -25,6 +24,9 @@ public: signals: void emptied(MarkerGroup*); +public slots: + bool remove(Marker *m); + private: void markerMoved(double newpos); diff --git a/Software/PC_Application/Traces/Marker/markerwidget.cpp b/Software/PC_Application/Traces/Marker/markerwidget.cpp index bc4bfde..2274d10 100644 --- a/Software/PC_Application/Traces/Marker/markerwidget.cpp +++ b/Software/PC_Application/Traces/Marker/markerwidget.cpp @@ -5,6 +5,7 @@ #include #include +#include MarkerWidget::MarkerWidget(MarkerModel &model, QWidget *parent) : QWidget(parent), @@ -12,6 +13,16 @@ MarkerWidget::MarkerWidget(MarkerModel &model, QWidget *parent) : model(model) { ui->setupUi(this); + + // some image magic to create a button with three "add" icons (not available as standard icon) + QImage image(44, 44, QImage::Format_ARGB32); + auto origImage = ui->bAddAll->icon().pixmap(22).toImage().convertToFormat(QImage::Format_ARGB32); + QPainter painter(&image); + painter.drawImage(0, 0, origImage); + painter.drawImage(origImage.width(), 0, origImage); + painter.drawImage(origImage.width()/2, origImage.height(), origImage); + ui->bAddAll->setIcon(QIcon(QPixmap::fromImage(image))); + ui->treeView->setModel(&model); ui->treeView->setItemDelegateForColumn(MarkerModel::ColIndexTrace, new MarkerTraceDelegate); ui->treeView->setItemDelegateForColumn(MarkerModel::ColIndexType, new MarkerTypeDelegate); @@ -47,7 +58,7 @@ MarkerWidget::MarkerWidget(MarkerModel &model, QWidget *parent) : } // multiple markers selected, execute group context menu QMenu menu; - auto createGroup = new QAction("Link selected"); + auto createGroup = new QAction("Link selected", &menu); connect(createGroup, &QAction::triggered, [&](){ auto g = model.createMarkerGroup(); // assign markers to group @@ -57,7 +68,7 @@ MarkerWidget::MarkerWidget(MarkerModel &model, QWidget *parent) : }); menu.addAction(createGroup); if(anyInGroup) { - auto removeGroup = new QAction("Break Links"); + auto removeGroup = new QAction("Break Links", &menu); connect(removeGroup, &QAction::triggered, [&](){ // remove selected markers from groups if they are already assigned to one for(auto m : selected) { @@ -123,6 +134,18 @@ void MarkerWidget::on_bAdd_clicked() model.addMarker(marker); } +void MarkerWidget::on_bAddAll_clicked() +{ + // add a marker for every trace and link them + auto group = model.createMarkerGroup(); + for(auto trace : model.getModel().getTraces()) { + auto m = model.createDefaultMarker(); + m->assignTrace(trace); + group->add(m); + model.addMarker(m); + } +} + bool MarkerWidget::eventFilter(QObject *, QEvent *event) { if (event->type() == QEvent::KeyPress) { @@ -152,4 +175,3 @@ void MarkerWidget::updatePersistentEditors() } } } - diff --git a/Software/PC_Application/Traces/Marker/markerwidget.h b/Software/PC_Application/Traces/Marker/markerwidget.h index 29b2aca..7e8f293 100644 --- a/Software/PC_Application/Traces/Marker/markerwidget.h +++ b/Software/PC_Application/Traces/Marker/markerwidget.h @@ -22,6 +22,8 @@ private slots: void on_bAdd_clicked(); void updatePersistentEditors(); + void on_bAddAll_clicked(); + private: bool eventFilter(QObject *obj, QEvent *event) override; Ui::MarkerWidget *ui; diff --git a/Software/PC_Application/Traces/Marker/markerwidget.ui b/Software/PC_Application/Traces/Marker/markerwidget.ui index e7265b2..7caf113 100644 --- a/Software/PC_Application/Traces/Marker/markerwidget.ui +++ b/Software/PC_Application/Traces/Marker/markerwidget.ui @@ -44,7 +44,27 @@ - Add + Add marker + + + + + + + :/icons/add.png:/icons/add.png + + + + + + + + 0 + 0 + + + + Add markers to all traces @@ -64,7 +84,7 @@ - Delete + Delete marker diff --git a/Software/PC_Application/Traces/Math/dft.cpp b/Software/PC_Application/Traces/Math/dft.cpp index b2d3990..6e4d3c6 100644 --- a/Software/PC_Application/Traces/Math/dft.cpp +++ b/Software/PC_Application/Traces/Math/dft.cpp @@ -42,6 +42,9 @@ void Math::DFT::edit() auto d = new QDialog(); auto ui = new Ui::DFTDialog; ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); ui->windowBox->setLayout(new QVBoxLayout); ui->windowBox->layout()->addWidget(window.createEditor()); @@ -76,6 +79,9 @@ QWidget *Math::DFT::createExplanationWidget() auto w = new QWidget(); auto ui = new Ui::DFTExplanationWidget; ui->setupUi(w); + connect(w, &QWidget::destroyed, [=](){ + delete ui; + }); return w; } diff --git a/Software/PC_Application/Traces/Math/expression.cpp b/Software/PC_Application/Traces/Math/expression.cpp index 8d05357..0f26417 100644 --- a/Software/PC_Application/Traces/Math/expression.cpp +++ b/Software/PC_Application/Traces/Math/expression.cpp @@ -37,6 +37,9 @@ void Math::Expression::edit() auto d = new QDialog(); auto ui = new Ui::ExpressionDialog; ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); ui->expEdit->setText(exp); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ exp = ui->expEdit->text(); @@ -57,6 +60,9 @@ QWidget *Math::Expression::createExplanationWidget() auto w = new QWidget(); auto ui = new Ui::ExpressionExplanationWidget; ui->setupUi(w); + connect(w, &QWidget::destroyed, [=](){ + delete ui; + }); return w; } diff --git a/Software/PC_Application/Traces/Math/medianfilter.cpp b/Software/PC_Application/Traces/Math/medianfilter.cpp index e72a3aa..6e84cbb 100644 --- a/Software/PC_Application/Traces/Math/medianfilter.cpp +++ b/Software/PC_Application/Traces/Math/medianfilter.cpp @@ -29,6 +29,9 @@ void MedianFilter::edit() auto d = new QDialog(); auto ui = new Ui::MedianFilterDialog(); ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); ui->kernelSize->setValue(kernelSize); ui->sortingMethod->setCurrentIndex((int) order); @@ -52,6 +55,9 @@ QWidget *MedianFilter::createExplanationWidget() auto w = new QWidget(); auto ui = new Ui::MedianFilterExplanationWidget; ui->setupUi(w); + connect(w, &QWidget::destroyed, [=](){ + delete ui; + }); return w; } diff --git a/Software/PC_Application/Traces/Math/tdr.cpp b/Software/PC_Application/Traces/Math/tdr.cpp index 9ce2231..48111e4 100644 --- a/Software/PC_Application/Traces/Math/tdr.cpp +++ b/Software/PC_Application/Traces/Math/tdr.cpp @@ -52,6 +52,9 @@ void TDR::edit() auto d = new QDialog(); auto ui = new Ui::TDRDialog; ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); ui->windowBox->setLayout(new QVBoxLayout); ui->windowBox->layout()->addWidget(window.createEditor()); @@ -119,6 +122,9 @@ QWidget *TDR::createExplanationWidget() auto w = new QWidget(); auto ui = new Ui::TDRExplanationWidget; ui->setupUi(w); + connect(w, &QWidget::destroyed, [=](){ + delete ui; + }); return w; } diff --git a/Software/PC_Application/Traces/Math/timegate.cpp b/Software/PC_Application/Traces/Math/timegate.cpp index ebbe22f..c2f3af8 100644 --- a/Software/PC_Application/Traces/Math/timegate.cpp +++ b/Software/PC_Application/Traces/Math/timegate.cpp @@ -58,6 +58,9 @@ void Math::TimeGate::edit() auto d = new QDialog(); auto ui = new Ui::TimeGateDialog(); ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); ui->graph->setGate(this); ui->windowBox->setLayout(new QVBoxLayout); ui->windowBox->layout()->addWidget(window.createEditor()); @@ -113,6 +116,9 @@ QWidget *Math::TimeGate::createExplanationWidget() auto w = new QWidget(); auto ui = new Ui::TimeGateExplanationWidget; ui->setupUi(w); + connect(w, &QWidget::destroyed, [=](){ + delete ui; + }); return w; } diff --git a/Software/PC_Application/Traces/Math/tracemath.cpp b/Software/PC_Application/Traces/Math/tracemath.cpp index 2055c75..b15c8ba 100644 --- a/Software/PC_Application/Traces/Math/tracemath.cpp +++ b/Software/PC_Application/Traces/Math/tracemath.cpp @@ -74,6 +74,9 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type) ret.explanationWidget = new QWidget(); auto ui = new Ui::TimeDomainGatingExplanationWidget; ui->setupUi(ret.explanationWidget); + connect(ret.explanationWidget, &QWidget::destroyed, [=](){ + delete ui; + }); } break; default: @@ -84,7 +87,14 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type) TraceMath::Data TraceMath::getSample(unsigned int index) { - return data.at(index); + if(index < data.size()) { + return data[index]; + } else { + TraceMath::Data d; + d.x = 0; + d.y = 0; + return d; + } } double TraceMath::getStepResponse(unsigned int index) diff --git a/Software/PC_Application/Traces/traceeditdialog.cpp b/Software/PC_Application/Traces/traceeditdialog.cpp index 0f9e21d..15f15d1 100644 --- a/Software/PC_Application/Traces/traceeditdialog.cpp +++ b/Software/PC_Application/Traces/traceeditdialog.cpp @@ -169,6 +169,9 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : auto d = new QDialog(); auto ui = new Ui::NewTraceMathDialog(); ui->setupUi(d); + connect(d, &QDialog::finished, [=](){ + delete ui; + }); for(int i = 0; i < (int) TraceMath::Type::Last;i++) { auto info = TraceMath::getInfo(static_cast(i)); ui->list->addItem(info.name); diff --git a/Software/PC_Application/Traces/traceplot.cpp b/Software/PC_Application/Traces/traceplot.cpp index a7c9398..609b03f 100644 --- a/Software/PC_Application/Traces/traceplot.cpp +++ b/Software/PC_Application/Traces/traceplot.cpp @@ -165,13 +165,12 @@ void TracePlot::paintEvent(QPaintEvent *event) continue; } + // Trace name auto textArea = QRect(x, areaTextTop, width() - x, marginTop); - QFont font = p.font(); font.setPixelSize(12); p.setFont(font); p.setPen(t.first->color()); - auto space = " "; auto label = space + t.first->name() + space; QRectF usedLabelArea = p.boundingRect(textArea, 0, label); @@ -185,9 +184,6 @@ void TracePlot::paintEvent(QPaintEvent *event) x += usedLabelArea.width()+labelMarginRight; auto tmarkers = t.first->getMarkers(); - - font.setPixelSize(12); - p.setFont(font); for(auto m : tmarkers) { if(!xCoordinateVisible(m->getPosition())) { // marker not visible with current plot settings @@ -199,9 +195,9 @@ void TracePlot::paintEvent(QPaintEvent *event) } hasMarkerData = true; + // Rounded box auto space = " "; auto textArea = QRect(width() - marginRight - marginMarkerData, y, width() - marginRight, y + 100); - auto description = m->getSuffix() + space + m->readablePosition(); auto label = space + QString::number(m->getNumber()) + space; QRectF textAreaConsumed = p.boundingRect(textArea, 0, label); QPainterPath pathM; @@ -209,10 +205,14 @@ void TracePlot::paintEvent(QPaintEvent *event) p.fillPath(pathM, t.first->color()); p.drawPath(pathM); + // Over box p.setPen(Util::getFontColorFromBackground(t.first->color())); p.drawText(textArea, 0, label); + + // Non-rounded description + auto description = m->getSuffix() + space + m->readablePosition(); p.setPen(t.first->color()); - p.drawText(textAreaConsumed.x()+textAreaConsumed.width(), textAreaConsumed.y(), textArea.width(), textArea.height(), 0, description); + p.drawText(width() - marginRight - marginMarkerData + textAreaConsumed.width() + 5, textAreaConsumed.y(), width() - marginRight, textArea.height(), 0, description); y += textAreaConsumed.height(); for(auto f : m->getGraphDisplayFormats()) { diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index 949430e..70213ed 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -186,7 +186,7 @@ void TraceSmithChart::draw(QPainter &p) { // trace marked invisible continue; } - pen = QPen(trace->color(), 1); + pen = QPen(trace->color(), pref.Graphs.lineWidth); pen.setCosmetic(true); p.setPen(pen); int nPoints = trace->size(); diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index 56f6e75..bbfcf8d 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -17,22 +17,6 @@ using namespace std; -const set TraceXYPlot::YAxisTypes = {TraceXYPlot::YAxisType::Disabled, - TraceXYPlot::YAxisType::Magnitude, - TraceXYPlot::YAxisType::Phase, - TraceXYPlot::YAxisType::VSWR, - TraceXYPlot::YAxisType::Real, - TraceXYPlot::YAxisType::Imaginary, - TraceXYPlot::YAxisType::SeriesR, - TraceXYPlot::YAxisType::Reactance, - TraceXYPlot::YAxisType::Capacitance, - TraceXYPlot::YAxisType::Inductance, - TraceXYPlot::YAxisType::QualityFactor, - TraceXYPlot::YAxisType::ImpulseReal, - TraceXYPlot::YAxisType::ImpulseMag, - TraceXYPlot::YAxisType::Step, - TraceXYPlot::YAxisType::Impedance}; - TraceXYPlot::TraceXYPlot(TraceModel &model, QWidget *parent) : TracePlot(model, parent) { @@ -333,8 +317,11 @@ void TraceXYPlot::updateContextMenu() bool TraceXYPlot::dropSupported(Trace *t) { - Q_UNUSED(t) - // all kind of traces can be dropped, the graph will be reconfigured to support the dropped trace if required + if(domainMatch(t) && !supported(t)) { + // correct domain configured but Y axis do not match, prevent drop + return false; + } + // either directly compatible or domain change required return true; } @@ -457,7 +444,7 @@ void TraceXYPlot::draw(QPainter &p) if(!t->isVisible()) { continue; } - pen = QPen(t->color(), 1); + pen = QPen(t->color(), pref.Graphs.lineWidth); pen.setCosmetic(true); if(i == 1) { pen.setStyle(Qt::DotLine); @@ -705,6 +692,9 @@ void TraceXYPlot::updateAxisTicks() double max = std::numeric_limits::lowest(); double min = std::numeric_limits::max(); for(auto t : tracesAxis[i]) { + if(!t->isVisible()) { + continue; + } unsigned int samples = t->size(); for(unsigned int j=0;joutputType() != Trace::DataType::Frequency) { - return false; - } - break; + return t->outputType() == Trace::DataType::Frequency; case XAxisType::Distance: case XAxisType::Time: - if(t->outputType() != Trace::DataType::Time) { - return false; - } - break; + return t->outputType() == Trace::DataType::Time; case XAxisType::Power: - if(t->outputType() != Trace::DataType::Power) { - return false; - } - break; - default: - break; + return t->outputType() == Trace::DataType::Power; + case XAxisType::Last: + return false; + } + return false; +} + +bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type) +{ + if(!domainMatch(t)) { + return false; } switch(type) { @@ -890,6 +885,11 @@ bool TraceXYPlot::supported(Trace *t, TraceXYPlot::YAxisType type) return false; } break; + case YAxisType::GroupDelay: + if(t->isReflection()) { + return false; + } + break; default: break; } @@ -939,6 +939,38 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo case YAxisType::QualityFactor: ret.setY(Util::SparamToQualityFactor(data.y)); break; + case YAxisType::GroupDelay: { + constexpr int requiredSamples = 5; + if(t->size() < requiredSamples) { + // unable to calculate + ret.setY(0.0); + break; + } + // needs at least some samples before/after current sample for calculating the derivative. + // For samples too far at either end of the trace, return group delay of "inner" trace sample instead + if(sample < requiredSamples / 2) { + return traceToCoordinate(t, requiredSamples / 2, type); + } else if(sample >= t->size() - requiredSamples / 2) { + return traceToCoordinate(t, t->size() - requiredSamples / 2 - 1, type); + } else { + // got enough samples at either end to calculate derivative. + // acquire phases of the required samples + std::vector phases; + phases.reserve(requiredSamples); + for(unsigned int index = sample - requiredSamples / 2;index <= sample + requiredSamples / 2;index++) { + phases.push_back(arg(t->sample(index).y)); + } + // make sure there are no phase jumps + Util::unwrapPhase(phases); + // calculate linearRegression to get derivative + double B_0, B_1; + Util::linearRegression(phases, B_0, B_1); + // B_1 now contains the derived phase vs. the sample. Scale by frequency to get group delay + double freq_step = t->sample(sample).x - t->sample(sample - 1).x; + ret.setY(-B_1 / (2.0*M_PI * freq_step)); + } + } + break; case YAxisType::ImpulseReal: ret.setY(real(data.y)); break; @@ -1096,6 +1128,7 @@ QString TraceXYPlot::AxisUnit(TraceXYPlot::YAxisType type) case TraceXYPlot::YAxisType::ImpulseMag: return "db"; case TraceXYPlot::YAxisType::Step: return ""; case TraceXYPlot::YAxisType::Impedance: return "Ohm"; + case TraceXYPlot::YAxisType::GroupDelay: return "s"; default: return ""; } } diff --git a/Software/PC_Application/Traces/tracexyplot.h b/Software/PC_Application/Traces/tracexyplot.h index 0bfd030..6d553a1 100644 --- a/Software/PC_Application/Traces/tracexyplot.h +++ b/Software/PC_Application/Traces/tracexyplot.h @@ -26,6 +26,7 @@ public: Capacitance, Inductance, QualityFactor, + GroupDelay, // TDR options ImpulseReal, ImpulseMag, @@ -33,7 +34,7 @@ public: Impedance, Last, }; - static const std::set YAxisTypes; + enum class XAxisType { Frequency, Time, @@ -80,6 +81,7 @@ private: YAxisType YAxisTypeFromName(QString name); XAxisMode AxisModeFromName(QString name); void enableTraceAxis(Trace *t, int axis, bool enabled); + bool domainMatch(Trace *t); bool supported(Trace *t) override; bool supported(Trace *t, YAxisType type); QPointF traceToCoordinate(Trace *t, unsigned int sample, YAxisType type); diff --git a/Software/PC_Application/Traces/xyplotaxisdialog.cpp b/Software/PC_Application/Traces/xyplotaxisdialog.cpp index 5c57bf7..46fbfdb 100644 --- a/Software/PC_Application/Traces/xyplotaxisdialog.cpp +++ b/Software/PC_Application/Traces/xyplotaxisdialog.cpp @@ -151,7 +151,8 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex) { auto type = (TraceXYPlot::XAxisType) XAxisIndex; auto supported = supportedYAxis(type); - for(auto t : TraceXYPlot::YAxisTypes) { + for(unsigned int i=0;i<(int) TraceXYPlot::YAxisType::Last;i++) { + auto t = (TraceXYPlot::YAxisType) i; auto enable = supported.count(t) > 0; auto index = (int) t; enableComboBoxItem(ui->Y1type, index, enable); @@ -197,6 +198,7 @@ std::set XYplotAxisDialog::supportedYAxis(TraceXYPlot::X ret.insert(TraceXYPlot::YAxisType::Capacitance); ret.insert(TraceXYPlot::YAxisType::Inductance); ret.insert(TraceXYPlot::YAxisType::QualityFactor); + ret.insert(TraceXYPlot::YAxisType::GroupDelay); break; case TraceXYPlot::XAxisType::Time: case TraceXYPlot::XAxisType::Distance: diff --git a/Software/PC_Application/Util/util.cpp b/Software/PC_Application/Util/util.cpp new file mode 100644 index 0000000..90baaef --- /dev/null +++ b/Software/PC_Application/Util/util.cpp @@ -0,0 +1,26 @@ +#include "util.h" + +void Util::unwrapPhase(std::vector &phase) +{ + for (unsigned int i = 1; i < phase.size(); i++) { + double d = phase[i] - phase[i-1]; + d = d > M_PI ? d - 2 * M_PI : (d < -M_PI ? d + 2 * M_PI : d); + phase[i] = phase[i-1] + d; + } +} + +void Util::linearRegression(const std::vector &input, double &B_0, double &B_1) +{ + double x_mean = (input.size() - 1.0) / 2.0; + double y_mean = std::accumulate(input.begin(), input.end(), 0.0) / input.size(); + double ss_xy = 0.0; + for(unsigned int i=0;i #include #include +#include #include @@ -55,6 +56,11 @@ namespace Util { auto brightness = q.redF() * 0.299 + q.greenF() * 0.587 + q.blueF() * 0.114; return brightness > 0.6 ? Qt::black : Qt::white; } + + void unwrapPhase(std::vector &phase); + + // input values are Y coordinates, assumes evenly spaced linear X values from 0 to input.size() - 1 + void linearRegression(const std::vector &input, double &B_0, double &B_1); } #endif // UTILH_H diff --git a/Software/PC_Application/VNA/Deembedding/deembedding.cpp b/Software/PC_Application/VNA/Deembedding/deembedding.cpp index 8dac4a0..ba9a166 100644 --- a/Software/PC_Application/VNA/Deembedding/deembedding.cpp +++ b/Software/PC_Application/VNA/Deembedding/deembedding.cpp @@ -34,6 +34,9 @@ void Deembedding::startMeasurementDialog(bool S11, bool S12, bool S21, bool S22) auto ui = new Ui_DeembeddingMeasurementDialog; measurementUI = ui; ui->setupUi(measurementDialog); + connect(measurementDialog, &QDialog::finished, [=](){ + delete ui; + }); // add the trace selector set skip; @@ -106,7 +109,8 @@ void Deembedding::startMeasurementDialog(bool S11, bool S12, bool S21, bool S22) Deembedding::Deembedding(TraceModel &tm) : tm(tm), - measuring(false) + measuring(false), + sweepPoints(0) { } diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp b/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp index f6592c0..78151ae 100644 --- a/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp +++ b/Software/PC_Application/VNA/Deembedding/matchingnetwork.cpp @@ -1,6 +1,8 @@ #include "matchingnetwork.h" #include "ui_matchingnetworkdialog.h" +#include "unit.h" +#include "CustomWidgets/informationbox.h" #include #include @@ -11,6 +13,7 @@ #include #include #include +#include using namespace std; @@ -68,6 +71,9 @@ void MatchingNetwork::edit() auto dialog = new QDialog(); auto ui = new Ui::MatchingNetworkDialog(); ui->setupUi(dialog); + connect(dialog, &QDialog::finished, [=](){ + delete ui; + }); dialog->setModal(true); graph = new QWidget(); @@ -83,24 +89,25 @@ void MatchingNetwork::edit() ui->lParallelC->installEventFilter(this); ui->lParallelL->installEventFilter(this); ui->lParallelR->installEventFilter(this); + ui->lDefinedThrough->installEventFilter(this); layout->setContentsMargins(0,0,0,0); layout->setSpacing(0); layout->addStretch(1); auto p1 = new QWidget(); - p1->setMinimumSize(portWidth, 150); - p1->setMaximumSize(portWidth, 150); + p1->setMinimumSize(portWidth, 151); + p1->setMaximumSize(portWidth, 151); p1->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - p1->setStyleSheet("image: url(:/icons/port1.svg);"); + p1->setStyleSheet("image: url(:/icons/port1.png);"); auto DUT = new QWidget(); - DUT->setMinimumSize(DUTWidth, 150); - DUT->setMaximumSize(DUTWidth, 150); + DUT->setMinimumSize(DUTWidth, 151); + DUT->setMaximumSize(DUTWidth, 151); DUT->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - DUT->setStyleSheet("image: url(:/icons/DUT.svg);"); + DUT->setStyleSheet("image: url(:/icons/DUT.png);"); auto p2 = new QWidget(); - p2->setMinimumSize(portWidth, 150); - p2->setMaximumSize(portWidth, 150); + p2->setMinimumSize(portWidth, 151); + p2->setMaximumSize(portWidth, 151); p2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - p2->setStyleSheet("image: url(:/icons/port2.svg);"); + p2->setStyleSheet("image: url(:/icons/port2.png);"); layout->addWidget(p1); for(auto w : p1Network) { layout->addWidget(w); @@ -136,27 +143,31 @@ void MatchingNetwork::edit() nlohmann::json MatchingNetwork::toJSON() { nlohmann::json j; - for(int i=0;i<2;i++) { - auto network = i==0 ? p1Network : p2Network; - nlohmann::json jn; - for(auto c : network) { - nlohmann::json jc; - jc["component"] = c->getName().toStdString(); - jc["params"] = c->toJSON(); - jn.push_back(jc); - } - j.push_back(jn); + nlohmann::json jn1, jn2; + for(auto c : p1Network) { + nlohmann::json jc; + jc["component"] = c->getName().toStdString(); + jc["params"] = c->toJSON(); + jn1.push_back(jc); } + for(auto c : p2Network) { + nlohmann::json jc; + jc["component"] = c->getName().toStdString(); + jc["params"] = c->toJSON(); + jn2.push_back(jc); + } + j["port1"] = jn1; + j["port2"] = jn2; + j["addNetwork"] = addNetwork; return j; } void MatchingNetwork::fromJSON(nlohmann::json j) { - for(int i=0;i<2;i++) { - auto jn = j[i]; - auto &network = i==0 ? p1Network : p2Network; - network.clear(); - for(auto jc : jn) { + p1Network.clear(); + p2Network.clear(); + if(j.contains("port1")) { + for(auto jc : j["port1"]) { if(!jc.contains("component")) { continue; } @@ -165,9 +176,23 @@ void MatchingNetwork::fromJSON(nlohmann::json j) continue; } c->fromJSON(jc["params"]); - network.push_back(c); + p1Network.push_back(c); } } + if(j.contains("port2")) { + for(auto jc : j["port2"]) { + if(!jc.contains("component")) { + continue; + } + auto c = MatchingComponent::createFromName(QString::fromStdString(jc["component"])); + if(!c) { + continue; + } + c->fromJSON(jc["params"]); + p2Network.push_back(c); + } + } + addNetwork = j.value("addNetwork", true); matching.clear(); } @@ -329,8 +354,8 @@ bool MatchingNetwork::eventFilter(QObject *object, QEvent *event) dropComponent = (MatchingComponent*) dropPtr; dragEvent->acceptProposedAction(); insertIndicator = new QWidget(); - insertIndicator->setMinimumSize(2, 150); - insertIndicator->setMaximumSize(2, 150); + insertIndicator->setMinimumSize(2, imageHeight); + insertIndicator->setMaximumSize(2, imageHeight); insertIndicator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); insertIndicator->setStyleSheet("background-color:red;"); updateInsertIndicator(dragEvent->pos().x()); @@ -368,6 +393,8 @@ bool MatchingNetwork::eventFilter(QObject *object, QEvent *event) dragComponent = new MatchingComponent(MatchingComponent::Type::ParallelL); } else if(object->objectName() == "lParallelR") { dragComponent = new MatchingComponent(MatchingComponent::Type::ParallelR); + } else if(object->objectName() == "lDefinedThrough") { + dragComponent = new MatchingComponent(MatchingComponent::Type::DefinedThrough); } else { dragComponent = nullptr; } @@ -397,18 +424,21 @@ bool MatchingNetwork::eventFilter(QObject *object, QEvent *event) MatchingComponent::MatchingComponent(Type type) { this->type = type; - setMinimumSize(150, 150); - setMaximumSize(150, 150); + eValue = nullptr; + touchstone = nullptr; + touchstoneLabel = nullptr; + setMinimumSize(151, 151); + setMaximumSize(151, 151); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - eValue = new SIUnitEdit(); - eValue->setPrecision(4); - eValue->setPrefixes("fpnum k"); - connect(eValue, &SIUnitEdit::valueChanged, this, &MatchingComponent::valueChanged); setFocusPolicy(Qt::FocusPolicy::ClickFocus); switch(type) { case Type::SeriesR: case Type::SeriesL: case Type::SeriesC: { + eValue = new SIUnitEdit(); + eValue->setPrecision(4); + eValue->setPrefixes("fpnum k"); + connect(eValue, &SIUnitEdit::valueChanged, this, &MatchingComponent::valueChanged); auto layout = new QVBoxLayout(); layout->addWidget(eValue); setLayout(layout); @@ -417,13 +447,16 @@ MatchingComponent::MatchingComponent(Type type) case Type::ParallelR: case Type::ParallelL: case Type::ParallelC: { + eValue = new SIUnitEdit(); + eValue->setPrecision(4); + eValue->setPrefixes("fpnum k"); + connect(eValue, &SIUnitEdit::valueChanged, this, &MatchingComponent::valueChanged); auto layout = new QVBoxLayout(); layout->addWidget(eValue); layout->addStretch(1); layout->setContentsMargins(9, 5, 9, 9); setLayout(layout); } - break; default: break; } @@ -431,38 +464,58 @@ MatchingComponent::MatchingComponent(Type type) case Type::SeriesR: eValue->setUnit("Ω"); eValue->setValue(50); - setStyleSheet("image: url(:/icons/seriesR.svg);"); + setStyleSheet("image: url(:/icons/seriesR.png);"); break; case Type::SeriesL: eValue->setUnit("H"); eValue->setValue(1e-9); - setStyleSheet("image: url(:/icons/seriesL.svg);"); + setStyleSheet("image: url(:/icons/seriesL.png);"); break; case Type::SeriesC: eValue->setUnit("F"); eValue->setValue(1e-12); - setStyleSheet("image: url(:/icons/seriesC.svg);"); + setStyleSheet("image: url(:/icons/seriesC.png);"); break; case Type::ParallelR: eValue->setUnit("Ω"); eValue->setValue(50); - setStyleSheet("image: url(:/icons/parallelR.svg);"); + setStyleSheet("image: url(:/icons/parallelR.png);"); break; case Type::ParallelL: eValue->setUnit("H"); eValue->setValue(1e-9); - setStyleSheet("image: url(:/icons/parallelL.svg);"); + setStyleSheet("image: url(:/icons/parallelL.png);"); break; case Type::ParallelC: eValue->setUnit("F"); eValue->setValue(1e-12); - setStyleSheet("image: url(:/icons/parallelC.svg);"); + setStyleSheet("image: url(:/icons/parallelC.png);"); + break; + case Type::DefinedThrough: { + touchstone = new Touchstone(2); + touchstoneLabel = new QLabel(); + touchstoneLabel->setWordWrap(true); + touchstoneLabel->setAlignment(Qt::AlignCenter); + auto layout = new QVBoxLayout(); + layout->addWidget(touchstoneLabel); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + setStyleSheet("image: url(:/icons/definedThrough.png);"); + updateTouchstoneLabel(); + } break; default: break; } } +MatchingComponent::~MatchingComponent() +{ + delete eValue; + delete touchstone; + delete touchstoneLabel; +} + ABCDparam MatchingComponent::parameters(double freq) { switch(type) { @@ -478,6 +531,15 @@ ABCDparam MatchingComponent::parameters(double freq) return ABCDparam(1.0, 0.0, 1.0/complex(0, freq * 2 * M_PI * eValue->value()), 1.0); case Type::ParallelC: return ABCDparam(1.0, 0.0, 1.0/complex(0, -1.0 / (freq * 2 * M_PI * eValue->value())), 1.0); + case Type::DefinedThrough: + if(touchstone->points() == 0 || freq < touchstone->minFreq() || freq > touchstone->maxFreq()) { + // outside of provided frequency range, pass through unchanged + return ABCDparam(1.0, 0.0, 0.0, 1.0); + } else { + auto d = touchstone->interpolate(freq); + auto S = Sparam(d.S[0], d.S[1], d.S[2], d.S[3]); + return ABCDparam(S, 50.0); + } default: return ABCDparam(1.0, 0.0, 0.0, 1.0); } @@ -485,7 +547,9 @@ ABCDparam MatchingComponent::parameters(double freq) void MatchingComponent::MatchingComponent::setValue(double v) { - eValue->setValue(v); + if(eValue) { + eValue->setValue(v); + } } MatchingComponent *MatchingComponent::createFromName(QString name) @@ -507,13 +571,73 @@ QString MatchingComponent::getName() nlohmann::json MatchingComponent::toJSON() { nlohmann::json j; - j["value"] = eValue->value(); + switch(type) { + case Type::SeriesC: + case Type::SeriesR: + case Type::SeriesL: + case Type::ParallelC: + case Type::ParallelR: + case Type::ParallelL: + j["value"] = eValue->value(); + break; + case Type::DefinedThrough: + j["touchstone"] = touchstone->toJSON(); + break; + case Type::Last: + break; + } return j; } void MatchingComponent::fromJSON(nlohmann::json j) { - eValue->setValue(j.value("value", 1e-12)); + switch(type) { + case Type::SeriesC: + case Type::SeriesR: + case Type::SeriesL: + case Type::ParallelC: + case Type::ParallelR: + case Type::ParallelL: + eValue->setValue(j.value("value", 1e-12)); + break; + case Type::DefinedThrough: + touchstone->fromJSON(j["touchstone"]); + updateTouchstoneLabel(); + break; + case Type::Last: + break; + } +} + +void MatchingComponent::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + if(type == Type::DefinedThrough) { + // select new touchstone file + auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s2p)", nullptr, QFileDialog::DontUseNativeDialog); + if (!filename.isEmpty()) { + try { + *touchstone = Touchstone::fromFile(filename.toStdString()); + } catch(const std::exception& e) { + InformationBox::ShowError("Failed to load file", QString("Attempt to load file ended with error: \"") + e.what()+"\""); + } + updateTouchstoneLabel(); + } + } +} + +void MatchingComponent::updateTouchstoneLabel() +{ + if(!touchstone || !touchstoneLabel) { + return; + } + if(touchstone->points() == 0) { + touchstoneLabel->setText("No data. Double-click to select touchstone file"); + } else { + QString text = QString::number(touchstone->points()) + " points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG", 4) + + " to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG", 4); + touchstoneLabel->setText(text); + } } QString MatchingComponent::typeToName(MatchingComponent::Type type) @@ -525,6 +649,7 @@ QString MatchingComponent::typeToName(MatchingComponent::Type type) case Type::ParallelR: return "ParallelR"; case Type::ParallelL: return "ParallelL"; case Type::ParallelC: return "ParallelC"; + case Type::DefinedThrough: return "Touchstone Through"; default: return ""; } } diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetwork.h b/Software/PC_Application/VNA/Deembedding/matchingnetwork.h index 7ea983e..afc1fc7 100644 --- a/Software/PC_Application/VNA/Deembedding/matchingnetwork.h +++ b/Software/PC_Application/VNA/Deembedding/matchingnetwork.h @@ -5,8 +5,10 @@ #include "deembeddingoption.h" #include "Tools/parameters.h" #include "savable.h" +#include "touchstone.h" #include +#include #include @@ -21,11 +23,13 @@ public: ParallelR, ParallelL, ParallelC, + DefinedThrough, // Add new matching components here, do not explicitly assign values and keep the Last entry at the last position Last, }; MatchingComponent(Type type); + ~MatchingComponent(); ABCDparam parameters(double freq); void setValue(double v); @@ -40,7 +44,11 @@ signals: void deleted(MatchingComponent* m); protected: SIUnitEdit *eValue; + Touchstone *touchstone; + QLabel *touchstoneLabel; private: + void mouseDoubleClickEvent(QMouseEvent *e) override; + void updateTouchstoneLabel(); static QString typeToName(Type type); Type type; void keyPressEvent(QKeyEvent *event) override; @@ -62,9 +70,10 @@ public: nlohmann::json toJSON() override; void fromJSON(nlohmann::json j) override; private: - static constexpr int componentWidth = 150; - static constexpr int DUTWidth = 150; - static constexpr int portWidth = 75; + static constexpr int imageHeight = 151; + static constexpr int componentWidth = 151; + static constexpr int DUTWidth = 151; + static constexpr int portWidth = 76; MatchingComponent *componentAtPosition(int pos); unsigned int findInsertPosition(int xcoord); void addComponentAtPosition(int pos, MatchingComponent *c); diff --git a/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui b/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui index 2a52e6f..a1c67f3 100644 --- a/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui +++ b/Software/PC_Application/VNA/Deembedding/matchingnetworkdialog.ui @@ -7,7 +7,7 @@ 0 0 772 - 442 + 443 @@ -131,20 +131,8 @@ Matching Components - - - 0 - - - 0 - - - 0 - - - 0 - - + + @@ -159,369 +147,423 @@ - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/seriesC.svg); - - 0 + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 0 + + Series C - - 0 + + Qt::AlignCenter - - - - border-image: url(:/icons/seriesC.svg); - - - QFrame::NoFrame - - - QFrame::Plain - - - Series C - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/seriesL.svg); - - 0 + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 0 + + Series L - - 0 + + Qt::AlignCenter - - - - border-image: url(:/icons/seriesL.svg); - - - QFrame::NoFrame - - - QFrame::Plain - - - Series L - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/seriesR.svg); - - 0 + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 0 + + Series R - - 0 + + Qt::AlignCenter - - - - border-image: url(:/icons/seriesR.svg); - - - QFrame::NoFrame - - - QFrame::Plain - - - Series R - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - - 80 - 80 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/definedThrough.svg); - - 0 + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 0 + + <html><head/><body><p align="center"><br/><br/>Defined<br/>Through</p></body></html> - - 0 + + Qt::AlignHCenter|Qt::AlignTop - - - - border-image: url(:/icons/parallelC.svg); - - - QFrame::NoFrame - - - QFrame::Plain - - - Parallel C - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/parallelC.svg); - - 0 + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 0 + + Parallel C - - 0 + + Qt::AlignHCenter|Qt::AlignTop - - - - - 0 - 0 - - - - border-image: url(:/icons/parallelL.svg); - - - QFrame::NoFrame - - - QFrame::Plain - - - Parallel L - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - - - - - - 0 - 0 - - - - - 80 - 80 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + - - 0 + + border-image: url(:/icons/parallelL.svg); - - 0 + + QFrame::NoFrame - - 0 + + QFrame::Plain - - 0 + + Parallel L - - - - border-image: url(:/icons/parallelR.svg); - - - QFrame::NoFrame - - - QFrame::Plain - - - Parallel R - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - - + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-image: url(:/icons/parallelR.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + Parallel R + + + Qt::AlignHCenter|Qt::AlignTop + + + + + diff --git a/Software/PC_Application/VNA/Deembedding/portextension.cpp b/Software/PC_Application/VNA/Deembedding/portextension.cpp index 8edbb80..b66556a 100644 --- a/Software/PC_Application/VNA/Deembedding/portextension.cpp +++ b/Software/PC_Application/VNA/Deembedding/portextension.cpp @@ -81,6 +81,9 @@ void PortExtension::edit() auto dialog = new QDialog(); ui = new Ui::PortExtensionEditDialog(); ui->setupUi(dialog); + connect(dialog, &QDialog::finished, [=](){ + delete ui; + }); // set initial values ui->P1Enabled->setChecked(port1.enabled); diff --git a/Software/PC_Application/VNA/Deembedding/twothru.cpp b/Software/PC_Application/VNA/Deembedding/twothru.cpp index ed1e9e8..22ed767 100644 --- a/Software/PC_Application/VNA/Deembedding/twothru.cpp +++ b/Software/PC_Application/VNA/Deembedding/twothru.cpp @@ -126,6 +126,9 @@ void TwoThru::edit() auto dialog = new QDialog(); ui = new Ui::TwoThruDialog(); ui->setupUi(dialog); + connect(dialog, &QDialog::finished, [=](){ + delete ui; + }); ui->Z0->setUnit("Ω"); ui->Z0->setPrecision(4); ui->Z0->setValue(Z0); diff --git a/Software/PC_Application/VNA/tracewidgetvna.cpp b/Software/PC_Application/VNA/tracewidgetvna.cpp index 2c9a824..725b985 100644 --- a/Software/PC_Application/VNA/tracewidgetvna.cpp +++ b/Software/PC_Application/VNA/tracewidgetvna.cpp @@ -83,6 +83,9 @@ void TraceWidgetVNA::importDialog() auto dialog = new QDialog(); auto ui = new Ui::s2pImportOptions; ui->setupUi(dialog); + connect(dialog, &QDialog::finished, [=](){ + delete ui; + }); ui->applyCal->setEnabled(calAvailable); ui->deembed->setEnabled(deembedAvailable); bool applyCal = false; diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 921172b..30bfa60 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -53,6 +53,7 @@ VNA::VNA(AppWindow *window) : Mode(window, "Vector Network Analyzer"), SCPINode("VNA"), deembedding(traceModel), + deembedding_active(false), central(new TileWidget(traceModel)) { averages = 1; @@ -60,6 +61,7 @@ VNA::VNA(AppWindow *window) calMeasuring = false; calDialog.reset(); calEdited = false; + settings.sweepType = SweepType::Frequency; // Create default traces auto tS11 = new Trace("S11", Qt::yellow); @@ -530,6 +532,12 @@ VNA::VNA(AppWindow *window) // Set initial sweep settings auto pref = Preferences::getInstance(); + if(pref.Acquisition.useMedianAveraging) { + average.setMode(Averaging::Mode::Median); + } else { + average.setMode(Averaging::Mode::Mean); + } + if(pref.Startup.RememberSweepSettings) { LoadSweepSettings(); } else { @@ -545,6 +553,8 @@ VNA::VNA(AppWindow *window) SetPoints(pref.Startup.DefaultSweep.points); if(pref.Startup.DefaultSweep.type == "Power Sweep") { SetSweepType(SweepType::Power); + } else { + SetSweepType(SweepType::Frequency); } } @@ -760,6 +770,11 @@ using namespace std; void VNA::NewDatapoint(Protocol::Datapoint d) { + if(d.pointNum >= settings.npoints) { + qWarning() << "Ignoring point with too large point number (" << d.pointNum << ")"; + return; + } + d = average.process(d); if(calMeasuring) { if(average.currentSweep() == averages) { @@ -1461,6 +1476,11 @@ void VNA::updateGraphColors() emit graphColorsChanged(); } +void VNA::setAveragingMode(Averaging::Mode mode) +{ + average.setMode(mode); +} + QString VNA::SweepTypeToString(VNA::SweepType sw) { switch(sw) { diff --git a/Software/PC_Application/VNA/vna.h b/Software/PC_Application/VNA/vna.h index c78f30a..dcb03d4 100644 --- a/Software/PC_Application/VNA/vna.h +++ b/Software/PC_Application/VNA/vna.h @@ -29,6 +29,7 @@ public: virtual void fromJSON(nlohmann::json j) override; void updateGraphColors(); + void setAveragingMode(Averaging::Mode mode); enum class SweepType { Frequency = 0, diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index 84eaa70..dc01824 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -78,6 +78,8 @@ AppWindow::AppWindow(QWidget *parent) // qDebug().setVerbosity(0); qDebug() << "Application start"; + this->setWindowIcon(QIcon(":/app/logo.png")); + parser.setApplicationDescription(qlibrevnaApp->applicationName()); parser.addHelpOption(); parser.addVersionOption(); @@ -202,6 +204,14 @@ AppWindow::AppWindow(QWidget *parent) vna->updateGraphColors(); } + // averaging mode may have changed, update for all relevant modes + if(p.Acquisition.useMedianAveraging) { + spectrumAnalyzer->setAveragingMode(Averaging::Mode::Median); + vna->setAveragingMode(Averaging::Mode::Median); + } else { + spectrumAnalyzer->setAveragingMode(Averaging::Mode::Mean); + vna->setAveragingMode(Averaging::Mode::Mean); + } }); connect(ui->actionAbout, &QAction::triggered, [=](){ diff --git a/Software/PC_Application/averaging.cpp b/Software/PC_Application/averaging.cpp index 8a5cffa..1eeb004 100644 --- a/Software/PC_Application/averaging.cpp +++ b/Software/PC_Application/averaging.cpp @@ -5,6 +5,7 @@ using namespace std; Averaging::Averaging() { averages = 1; + mode = Mode::Mean; } void Averaging::reset(unsigned int points) @@ -45,18 +46,57 @@ Protocol::Datapoint Averaging::process(Protocol::Datapoint d) deque->pop_front(); } - // calculate average - complex sum[4]; - for(auto s : *deque) { - sum[0] += s[0]; - sum[1] += s[1]; - sum[2] += s[2]; - sum[3] += s[3]; + switch(mode) { + case Mode::Mean: { + // calculate average + complex sum[4]; + for(auto s : *deque) { + sum[0] += s[0]; + sum[1] += s[1]; + sum[2] += s[2]; + sum[3] += s[3]; + } + S11 = sum[0] / (double) (deque->size()); + S12 = sum[1] / (double) (deque->size()); + S21 = sum[2] / (double) (deque->size()); + S22 = sum[3] / (double) (deque->size()); + } + break; + case Mode::Median: { + auto size = deque->size(); + // create sorted arrays + std::vector> S11sorted, S12sorted, S21sorted, S22sorted; + S11sorted.reserve(size); + S12sorted.reserve(size); + S21sorted.reserve(size); + S22sorted.reserve(size); + + auto comp = [=](const complex&a, const complex&b){ + return abs(a) < abs(b); + }; + + for(auto d : *deque) { + S11sorted.insert(upper_bound(S11sorted.begin(), S11sorted.end(), d[0], comp), d[0]); + S12sorted.insert(upper_bound(S12sorted.begin(), S12sorted.end(), d[1], comp), d[1]); + S21sorted.insert(upper_bound(S21sorted.begin(), S21sorted.end(), d[2], comp), d[2]); + S22sorted.insert(upper_bound(S22sorted.begin(), S22sorted.end(), d[3], comp), d[3]); + } + if(size & 0x01) { + // odd number of samples + S11 = S11sorted[size / 2]; + S12 = S12sorted[size / 2]; + S21 = S21sorted[size / 2]; + S22 = S22sorted[size / 2]; + } else { + // even number, use average of middle samples + S11 = (S11sorted[size / 2 - 1] + S11sorted[size / 2]) / 2.0; + S12 = (S12sorted[size / 2 - 1] + S12sorted[size / 2]) / 2.0; + S21 = (S21sorted[size / 2 - 1] + S21sorted[size / 2]) / 2.0; + S22 = (S22sorted[size / 2 - 1] + S22sorted[size / 2]) / 2.0; + } + } + break; } - S11 = sum[0] / (double) (deque->size()); - S12 = sum[1] / (double) (deque->size()); - S21 = sum[2] / (double) (deque->size()); - S22 = sum[3] / (double) (deque->size()); } d.real_S11 = S11.real(); @@ -90,16 +130,40 @@ Protocol::SpectrumAnalyzerResult Averaging::process(Protocol::SpectrumAnalyzerRe deque->pop_front(); } - // calculate average - complex sum[4]; - for(auto s : *deque) { - sum[0] += s[0]; - sum[1] += s[1]; - sum[2] += s[2]; - sum[3] += s[3]; + switch(mode) { + case Mode::Mean: { + // calculate average + complex sum[2]; + for(auto s : *deque) { + sum[0] += s[0]; + sum[1] += s[1]; + } + d.port1 = abs(sum[0] / (double) (deque->size())); + d.port2 = abs(sum[1] / (double) (deque->size())); + } + break; + case Mode::Median: { + auto size = deque->size(); + // create sorted arrays + std::vector port1, port2; + port1.reserve(size); + port2.reserve(size); + for(auto d : *deque) { + port1.insert(upper_bound(port1.begin(), port1.end(), abs(d[0])), abs(d[0])); + port2.insert(upper_bound(port2.begin(), port2.end(), abs(d[0])), abs(d[0])); + } + if(size & 0x01) { + // odd number of samples + d.port1 = port1[size / 2]; + d.port2 = port1[size / 2]; + } else { + // even number, use average of middle samples + d.port1 = (port1[size / 2 - 1] + port1[size / 2]) / 2; + d.port2 = (port2[size / 2 - 1] + port2[size / 2]) / 2; + } + } + break; } - d.port1 = abs(sum[0] / (double) (deque->size())); - d.port2 = abs(sum[1] / (double) (deque->size())); } return d; @@ -122,3 +186,13 @@ unsigned int Averaging::currentSweep() return 0; } } + +Averaging::Mode Averaging::getMode() const +{ + return mode; +} + +void Averaging::setMode(const Mode &value) +{ + mode = value; +} diff --git a/Software/PC_Application/averaging.h b/Software/PC_Application/averaging.h index 7f5a768..84dc68e 100644 --- a/Software/PC_Application/averaging.h +++ b/Software/PC_Application/averaging.h @@ -10,6 +10,11 @@ class Averaging { public: + enum class Mode { + Mean, + Median + }; + Averaging(); void reset(unsigned int points); void setAverages(unsigned int a); @@ -21,10 +26,14 @@ public: // Returns the number of the currently active sweep. Value is incremented whenever the the first point of the sweep is added // Returned values are in range 0 (when no data has been added yet) to averages unsigned int currentSweep(); + Mode getMode() const; + void setMode(const Mode &value); + private: std::vector, 4>>> avg; int maxPoints; unsigned int averages; + Mode mode; }; #endif // AVERAGING_H diff --git a/Software/PC_Application/icons.qrc b/Software/PC_Application/icons.qrc index b4b2c2f..148fb55 100644 --- a/Software/PC_Application/icons.qrc +++ b/Software/PC_Application/icons.qrc @@ -50,5 +50,13 @@ icons/down.png icons/up.png icons/chainlink.png + icons/definedThrough.svg + icons/seriesR.png + icons/seriesL.png + icons/seriesC.png + icons/parallelR.png + icons/parallelL.png + icons/parallelC.png + icons/definedThrough.png diff --git a/Software/PC_Application/icons/definedThrough.png b/Software/PC_Application/icons/definedThrough.png new file mode 100644 index 0000000..71b4182 Binary files /dev/null and b/Software/PC_Application/icons/definedThrough.png differ diff --git a/Software/PC_Application/icons/definedThrough.svg b/Software/PC_Application/icons/definedThrough.svg new file mode 100644 index 0000000..c50ab38 --- /dev/null +++ b/Software/PC_Application/icons/definedThrough.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/parallelC.png b/Software/PC_Application/icons/parallelC.png new file mode 100644 index 0000000..9acfd38 Binary files /dev/null and b/Software/PC_Application/icons/parallelC.png differ diff --git a/Software/PC_Application/icons/parallelL.png b/Software/PC_Application/icons/parallelL.png new file mode 100644 index 0000000..5e7b85a Binary files /dev/null and b/Software/PC_Application/icons/parallelL.png differ diff --git a/Software/PC_Application/icons/parallelR.png b/Software/PC_Application/icons/parallelR.png new file mode 100644 index 0000000..78feadc Binary files /dev/null and b/Software/PC_Application/icons/parallelR.png differ diff --git a/Software/PC_Application/icons/port1.svg b/Software/PC_Application/icons/port1.svg index c93f08d..12252e3 100644 --- a/Software/PC_Application/icons/port1.svg +++ b/Software/PC_Application/icons/port1.svg @@ -1,6 +1,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -12,6 +32,17 @@ + + + + + + + + + + + diff --git a/Software/PC_Application/icons/port2.svg b/Software/PC_Application/icons/port2.svg index f98b346..9ead629 100644 --- a/Software/PC_Application/icons/port2.svg +++ b/Software/PC_Application/icons/port2.svg @@ -1,12 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/seriesC.png b/Software/PC_Application/icons/seriesC.png new file mode 100644 index 0000000..3e5155a Binary files /dev/null and b/Software/PC_Application/icons/seriesC.png differ diff --git a/Software/PC_Application/icons/seriesL.png b/Software/PC_Application/icons/seriesL.png new file mode 100644 index 0000000..ae373ce Binary files /dev/null and b/Software/PC_Application/icons/seriesL.png differ diff --git a/Software/PC_Application/icons/seriesR.png b/Software/PC_Application/icons/seriesR.png new file mode 100644 index 0000000..f4028ee Binary files /dev/null and b/Software/PC_Application/icons/seriesR.png differ diff --git a/Software/PC_Application/preferences.cpp b/Software/PC_Application/preferences.cpp index e7b6696..81db186 100644 --- a/Software/PC_Application/preferences.cpp +++ b/Software/PC_Application/preferences.cpp @@ -134,6 +134,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : p->Acquisition.harmonicMixing = ui->AcquisitionUseHarmonic->isChecked(); p->Acquisition.useDFTinSAmode = ui->AcquisitionUseDFT->isChecked(); p->Acquisition.RBWLimitForDFT = ui->AcquisitionDFTlimitRBW->value(); + p->Acquisition.useMedianAveraging = ui->AcquisitionAveragingMode->currentIndex() == 1; p->Graphs.Color.background = ui->GraphsColorBackground->getColor(); p->Graphs.Color.axis = ui->GraphsColorAxis->getColor(); p->Graphs.Color.Ticks.Background.enabled = ui->GraphsColorTicksBackgroundEnabled->isChecked(); @@ -142,6 +143,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : p->Graphs.domainChangeBehavior = (GraphDomainChangeBehavior) ui->GraphsDomainChangeBehavior->currentIndex(); p->Graphs.markerBehavior.showDataOnGraphs = ui->GraphsShowMarkerData->isChecked(); p->Graphs.markerBehavior.showAllData = ui->GraphsShowAllMarkerData->isChecked(); + p->Graphs.lineWidth = ui->GraphsLineWidth->value(); p->SCPIServer.enabled = ui->SCPIServerEnabled->isChecked(); p->SCPIServer.port = ui->SCPIServerPort->value(); accept(); @@ -199,6 +201,7 @@ void PreferencesDialog::setInitialGUIState() ui->AcquisitionUseHarmonic->setChecked(p->Acquisition.harmonicMixing); ui->AcquisitionUseDFT->setChecked(p->Acquisition.useDFTinSAmode); ui->AcquisitionDFTlimitRBW->setValue(p->Acquisition.RBWLimitForDFT); + ui->AcquisitionAveragingMode->setCurrentIndex(p->Acquisition.useMedianAveraging ? 1 : 0); ui->GraphsColorBackground->setColor(p->Graphs.Color.background); ui->GraphsColorAxis->setColor(p->Graphs.Color.axis); @@ -208,6 +211,8 @@ void PreferencesDialog::setInitialGUIState() ui->GraphsDomainChangeBehavior->setCurrentIndex((int) p->Graphs.domainChangeBehavior); ui->GraphsShowMarkerData->setChecked(p->Graphs.markerBehavior.showDataOnGraphs); ui->GraphsShowAllMarkerData->setChecked(p->Graphs.markerBehavior.showAllData); + ui->GraphsLineWidth->setValue(p->Graphs.lineWidth); + ui->SCPIServerEnabled->setChecked(p->SCPIServer.enabled); ui->SCPIServerPort->setValue(p->SCPIServer.port); diff --git a/Software/PC_Application/preferences.h b/Software/PC_Application/preferences.h index 698b8f8..35f0444 100644 --- a/Software/PC_Application/preferences.h +++ b/Software/PC_Application/preferences.h @@ -66,6 +66,7 @@ public: bool harmonicMixing; bool useDFTinSAmode; double RBWLimitForDFT; + bool useMedianAveraging; } Acquisition; struct { struct { @@ -84,6 +85,7 @@ public: bool showDataOnGraphs; bool showAllData; } markerBehavior; + double lineWidth; } Graphs; struct { bool enabled; @@ -100,7 +102,7 @@ private: QString name; QVariant def; }; - const std::array descr = {{ + const std::array descr = {{ {&Startup.ConnectToFirstDevice, "Startup.ConnectToFirstDevice", true}, {&Startup.RememberSweepSettings, "Startup.RememberSweepSettings", false}, {&Startup.DefaultSweep.type, "Startup.DefaultSweep.type", "Frequency"}, @@ -128,6 +130,7 @@ private: {&Acquisition.harmonicMixing, "Acquisition.harmonicMixing", false}, {&Acquisition.useDFTinSAmode, "Acquisition.useDFTinSAmode", true}, {&Acquisition.RBWLimitForDFT, "Acquisition.RBWLimitForDFT", 3000.0}, + {&Acquisition.useMedianAveraging, "Acquisition.useMedianAveraging", false}, {&Graphs.Color.background, "Graphs.Color.background", QColor(Qt::black)}, {&Graphs.Color.axis, "Graphs.Color.axis", QColor(Qt::white)}, {&Graphs.Color.Ticks.Background.enabled, "Graphs.Color.Ticks.Background.enabled", true}, @@ -136,6 +139,7 @@ private: {&Graphs.domainChangeBehavior, "Graphs.domainChangeBehavior", GraphDomainChangeBehavior::AdjustGraphs}, {&Graphs.markerBehavior.showDataOnGraphs, "Graphs.markerBehavior.ShowDataOnGraphs", true}, {&Graphs.markerBehavior.showAllData, "Graphs.markerBehavior.ShowAllData", false}, + {&Graphs.lineWidth, "Graphs.lineWidth", 1.0}, {&SCPIServer.enabled, "SCPIServer.enabled", true}, {&SCPIServer.port, "SCPIServer.port", 19542}, }}; diff --git a/Software/PC_Application/preferencesdialog.ui b/Software/PC_Application/preferencesdialog.ui index 23b3fb5..1cd937a 100644 --- a/Software/PC_Application/preferencesdialog.ui +++ b/Software/PC_Application/preferencesdialog.ui @@ -637,6 +637,36 @@ + + + + Common + + + + + + Averaging mode: + + + + + + + + Mean + + + + + Median + + + + + + + @@ -739,6 +769,20 @@ + + + + Line Width: + + + + + + + 0.100000000000000 + + + diff --git a/Software/PC_Application/resources/banner.png b/Software/PC_Application/resources/banner.png new file mode 100644 index 0000000..c6a617e Binary files /dev/null and b/Software/PC_Application/resources/banner.png differ diff --git a/Software/PC_Application/resources/librevna.desktop b/Software/PC_Application/resources/librevna.desktop new file mode 100644 index 0000000..acd7cf7 --- /dev/null +++ b/Software/PC_Application/resources/librevna.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=LibreVNA GUI +Comment=Vector Network Analyzer for LibreVNA board +Exec=/opt/LibreVNA-GUI +Terminal=false +Type=Application +StartupNotify=true +Categories=Science;Engineering;Electronics +Icon=librevna.png diff --git a/Software/PC_Application/resources/librevna.png b/Software/PC_Application/resources/librevna.png new file mode 100644 index 0000000..71c7713 Binary files /dev/null and b/Software/PC_Application/resources/librevna.png differ diff --git a/Software/PC_Application/resources/librevna.qrc b/Software/PC_Application/resources/librevna.qrc new file mode 100644 index 0000000..b535e6a --- /dev/null +++ b/Software/PC_Application/resources/librevna.qrc @@ -0,0 +1,5 @@ + + + librevna.png + + diff --git a/Software/PC_Application/resources/librevna.svg b/Software/PC_Application/resources/librevna.svg new file mode 100644 index 0000000..d0a6b51 --- /dev/null +++ b/Software/PC_Application/resources/librevna.svg @@ -0,0 +1,32 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/touchstone.cpp b/Software/PC_Application/touchstone.cpp index 555667e..5525dbe 100644 --- a/Software/PC_Application/touchstone.cpp +++ b/Software/PC_Application/touchstone.cpp @@ -384,3 +384,57 @@ QString Touchstone::getFilename() const { return filename; } + +nlohmann::json Touchstone::toJSON() +{ + nlohmann::json j; + j["ports"] = m_ports; + j["filename"] = filename.toStdString(); + if(m_datapoints.size() > 0) { + nlohmann::json json_points; + for(auto d : m_datapoints) { + nlohmann::json point; + point["frequency"] = d.frequency; + nlohmann::json sparams; + for(auto s : d.S) { + nlohmann::json sparam; + sparam["real"] = s.real(); + sparam["imag"] = s.imag(); + sparams.push_back(sparam); + } + point["Sparams"] = sparams; + json_points.push_back(point); + } + j["datapoints"] = json_points; + } + return j; +} + +void Touchstone::fromJSON(nlohmann::json j) +{ + m_datapoints.clear(); + filename = QString::fromStdString(j.value("filename", "")); + m_ports = j.value("ports", 0); + if(!m_ports || !j.contains("datapoints")) { + return; + } + auto json_points = j["datapoints"]; + for(auto point : json_points) { + Datapoint d; + if(!point.contains("frequency") || !point.contains("Sparams")) { + // missing data, abort here + qWarning() << "Touchstone data point does not contain frequency or S parameters"; + break; + } + d.frequency = point["frequency"]; + if(point["Sparams"].size() != m_ports * m_ports) { + // invalid number of Sparams, abort here + qWarning() << "Invalid number of S parameters, got" << point["Sparams"].size() << "expected" << m_ports*m_ports; + break; + } + for(auto Sparam : point["Sparams"]) { + d.S.push_back(complex(Sparam.value("real", 0.0), Sparam.value("imag", 0.0))); + } + m_datapoints.push_back(d); + } +} diff --git a/Software/PC_Application/touchstone.h b/Software/PC_Application/touchstone.h index a73b4ce..fa34fa1 100644 --- a/Software/PC_Application/touchstone.h +++ b/Software/PC_Application/touchstone.h @@ -1,12 +1,14 @@ #ifndef TOUCHSTONE_H #define TOUCHSTONE_H +#include "savable.h" + #include #include #include #include -class Touchstone +class Touchstone : public Savable { public: enum class Scale { @@ -29,6 +31,7 @@ public: }; Touchstone(unsigned int m_ports); + virtual ~Touchstone(){}; 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); @@ -45,6 +48,9 @@ public: unsigned int ports() { return m_ports; } QString getFilename() const; + virtual nlohmann::json toJSON(); + virtual void fromJSON(nlohmann::json j); + private: unsigned int m_ports; std::vector m_datapoints; diff --git a/Software/VNA_embedded/.cproject b/Software/VNA_embedded/.cproject index c9b8014..41f3bde 100644 --- a/Software/VNA_embedded/.cproject +++ b/Software/VNA_embedded/.cproject @@ -189,7 +189,7 @@ - + diff --git a/Software/VNA_embedded/.settings/language.settings.xml b/Software/VNA_embedded/.settings/language.settings.xml index aadbc97..17b53d6 100644 --- a/Software/VNA_embedded/.settings/language.settings.xml +++ b/Software/VNA_embedded/.settings/language.settings.xml @@ -11,7 +11,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/Software/VNA_embedded/Application/Drivers/max2871.cpp b/Software/VNA_embedded/Application/Drivers/max2871.cpp index 9781898..54fa29b 100644 --- a/Software/VNA_embedded/Application/Drivers/max2871.cpp +++ b/Software/VNA_embedded/Application/Drivers/max2871.cpp @@ -190,6 +190,14 @@ bool MAX2871::SetFrequency(uint64_t f) { auto approx = Algorithm::BestRationalApproximation(fraction, 4095); + if (approx.denom == approx.num) { + // got an impossible result due to floating point limitations(?) + // Set fractional part to zero, increase integer part instead + approx.num = 0; + approx.denom = 2; + N++; + } + if(approx.denom == 1) { // M value must be at least 2 approx.denom = 2; diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp index 0d1d973..e11da5e 100644 --- a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp @@ -300,6 +300,10 @@ void SA::Work() { if(!s.SignalID || signalIDstep >= signalIDsteps - 1) { // this measurement point is done, handle result according to detector for(uint16_t i=0;i= points) { + // DFT covered more points than are required for the remaining sweep, can abort here + break; + } uint16_t binIndex = (pointCnt + i) / binSize; uint32_t pointInBin = (pointCnt + i) % binSize; bool lastPointInBin = pointInBin >= binSize - 1; @@ -386,7 +390,7 @@ void SA::Work() { Communication::Send(packet); } - if(pointCnt < points - DFTpoints) { + if(pointCnt + DFTpoints < points) { pointCnt += DFTpoints; } else { pointCnt = 0; diff --git a/Software/VNA_embedded/Application/VNA.cpp b/Software/VNA_embedded/Application/VNA.cpp index 9e0b4c3..65b0973 100644 --- a/Software/VNA_embedded/Application/VNA.cpp +++ b/Software/VNA_embedded/Application/VNA.cpp @@ -180,9 +180,11 @@ bool VNA::Setup(Protocol::SweepSettings s) { // Configure LO2 for the changed IF1. This is not necessary right now but it will generate // the correct clock settings last_LO2 = actualFirstIF - HW::IF2; - LOG_INFO("Changing 2.LO to %lu at point %lu (%lu%06luHz) to reach correct 2.IF frequency", + LOG_INFO("Changing 2.LO to %lu at point %lu (%lu%06luHz) to reach correct 2.IF frequency (1.LO: %lu%06luHz, 1.IF: %lu%06luHz)", last_LO2, i, (uint32_t ) (freq / 1000000), - (uint32_t ) (freq % 1000000)); + (uint32_t ) (freq % 1000000), (uint32_t ) (actualLO1 / 1000000), + (uint32_t ) (actualLO1 % 1000000), (uint32_t ) (actualFirstIF / 1000000), + (uint32_t ) (actualFirstIF % 1000000)); } else { // last entry in IF table, revert LO2 to default last_LO2 = HW::IF1 - HW::IF2; diff --git a/Software/VNA_embedded/Makefile b/Software/VNA_embedded/Makefile index 8f06c5a..ec8f4f3 100644 --- a/Software/VNA_embedded/Makefile +++ b/Software/VNA_embedded/Makefile @@ -101,7 +101,7 @@ MCU = $(CPU) -mthumb $(FLOAT-ABI) $(FPU) C_DEFS = \ -DFW_MAJOR=1 \ -DFW_MINOR=2 \ --DFW_PATCH=0 \ +-DFW_PATCH=1 \ -DUSE_FULL_LL_DRIVER \ -DHW_REVISION="'B'" \ -D__weak="__attribute__((weak))" \