qucs_s/qucs/dialogs/matchdialog.cpp

2102 lines
78 KiB
C++

/***************************************************************************
matchdialog.cpp
-----------------
begin : Fri Jul 22 2005
copyright : (C) 2005 by Michael Margraf
email : michael.margraf@alumni.tu-berlin.de
copyright : (C) 2012, 2013, 2016 by Qucs Team (see AUTHORS file)
-----------------------------------------------------------------------------
Update (2017) : New impedance matching techniques: Single matching,
double stub matching, real to real broadband transformers,
cascaded L-sections and lambda/8 + lambda/4 matching
Andres Martinez-Mera <andresmartinezmera@gmail.com>
Claudio Girardi <claudio.girardi@virgilio.it>
------------------------------------------------------------------------------
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "../../qucs-filter/material_props.h"
#include "main.h"
#include "matchdialog.h"
#include "misc.h"
#include "qucs.h"
#include <QApplication>
#include <QClipboard>
#include <QComboBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QRadioButton>
#include <QVBoxLayout>
MatchDialog::MatchDialog(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Create Matching Circuit"));
DoubleVal = new QDoubleValidator(this);
DoubleVal->setLocale(QLocale::C);
all = new QHBoxLayout(this);
// all->setSizeConstraint(QLayout::SetFixedSize);
/* The main frame was divided into two vertical layouts. The first one, on the
left side, is much the old matching tool whereas the other layout was
included specifically for microstrip synthesis.
*/
QVBoxLayout *matchFrame = new QVBoxLayout(); // Matching circuit design panel
QVBoxLayout *micro_layout = new QVBoxLayout(); // Substrate properties
all->addLayout(matchFrame);
all->addLayout(micro_layout);
MethodBox = new QGroupBox(tr("Implementation"));
matchFrame->addWidget(MethodBox);
MethodLayout = new QVBoxLayout();
SubstrateBox = new QGroupBox(tr("Microstrip Substrate"));
QGridLayout *hsubs = new QGridLayout();
SubstrateBox->setVisible(false);
micro_layout->addWidget(SubstrateBox);
SubstrateBox->setLayout(hsubs);
RelPermLabel = new QLabel(tr("Relative Permitivity"));
RelPermCombo = new QComboBox();
RelPermCombo->setEditable(true);
const char **p = List_er;
while (*(++p))
RelPermCombo->addItem(*p); // The dielectric coeff combobox is filled with
// the materials taken from
// "../../qucs-filter/material_props.h"
RelPermCombo->lineEdit()->setText("9.8");
hsubs->addWidget(RelPermLabel, 0, 0);
hsubs->addWidget(RelPermCombo, 0, 1, 1, 2);
subsHLabel = new QLabel(tr("Substrate height"));
SubHeightEdit = new QLineEdit("1.0");
SubsHScale = new QLabel("mm");
hsubs->addWidget(subsHLabel, 1, 0);
hsubs->addWidget(SubHeightEdit, 1, 1);
hsubs->addWidget(SubsHScale, 1, 2);
// Thickness
thicknessLabel = new QLabel(tr("Metal thickness"));
thicknessEdit = new QLineEdit("12.5");
ThicknessScale = new QLabel("um");
hsubs->addWidget(thicknessLabel, 2, 0);
hsubs->addWidget(thicknessEdit, 2, 1);
hsubs->addWidget(ThicknessScale, 2, 2);
// Minimum width
minWLabel = new QLabel(tr("Minimum width"));
minWEdit = new QLineEdit("0.4");
minWScale = new QLabel("mm");
hsubs->addWidget(minWLabel, 3, 0);
hsubs->addWidget(minWEdit, 3, 1);
hsubs->addWidget(minWScale, 3, 2);
// Maximum width
maxWLabel = new QLabel(tr("Maximum width"));
maxWEdit = new QLineEdit("5.0");
maxWScale = new QLabel("mm");
hsubs->addWidget(maxWLabel, 4, 0);
hsubs->addWidget(maxWEdit, 4, 1);
hsubs->addWidget(maxWScale, 4, 2);
// tan(delta)
tanDLabel = new QLabel(tr("tanD"));
tanDEdit = new QLineEdit("0.0125");
hsubs->addWidget(tanDLabel, 5, 0);
hsubs->addWidget(tanDEdit, 5, 1);
// Resistivity
ResistivityLabel = new QLabel(tr("Resistivity"));
ResistivityEdit = new QLineEdit("2.43902e-08");
hsubs->addWidget(ResistivityLabel, 6, 0);
hsubs->addWidget(ResistivityEdit, 6, 1);
QHBoxLayout *h4 = new QHBoxLayout();
h4->setSpacing(3);
TopoLabel = new QLabel(tr("Method"));
h4->addWidget(TopoLabel);
// Matching network topology
TopoCombo = new QComboBox();
TopoCombo->setFixedWidth(220);
TopoCombo->addItem(tr("L-section"));
TopoCombo->addItem(tr("Single stub"));
TopoCombo->addItem(tr("Double stub"));
QString str = tr("Multistage ") + QString(QChar(0xBB, 0x03)) + "/4";
TopoCombo->addItem(str);
TopoCombo->addItem("Cascaded L-sections");
str = QString(QChar(0xBB, 0x03)) + "/8 +" + QString(QChar(0xBB, 0x03)) +
"/4 transformer";
TopoCombo->addItem(str);
h4->addWidget(TopoCombo);
connect(TopoCombo, SIGNAL(activated(int)), SLOT(slotChangeMode_TopoCombo()));
h4->addStretch(5);
MethodLayout->addLayout(h4);
// When the stub implementation is selected, it is possible to select either
// the open or the short circuit solution
QHBoxLayout *h5 = new QHBoxLayout();
OpenRadioButton = new QRadioButton(tr("Open stub"), this);
h5->addWidget(OpenRadioButton);
OpenRadioButton->setChecked(true);
OpenRadioButton->setVisible(false);
ShortRadioButton = new QRadioButton(tr("Short circuit stub"), this);
h5->addWidget(ShortRadioButton);
ShortRadioButton->setChecked(false);
ShortRadioButton->setVisible(false);
MethodLayout->addLayout(h5);
// Number of sections of the cascaded lambda/4 implementation
QHBoxLayout *h6 = new QHBoxLayout();
OrderLabel = new QLabel(tr("Number of sections"));
OrderEdit = new QLineEdit("3");
h6->addWidget(OrderLabel);
h6->addWidget(OrderEdit);
OrderLabel->setVisible(false);
OrderEdit->setVisible(false);
OrderEdit->setFixedWidth(50);
OrderEdit->setAlignment(Qt::AlignLeft);
h6->setAlignment(Qt::AlignLeft);
// Weighting: Binomial or Chebyshev
QHBoxLayout *h7 = new QHBoxLayout();
Weighting_groupBox = new QGroupBox(tr("Weighting"));
QHBoxLayout *h7_box = new QHBoxLayout();
QVBoxLayout *v7_box = new QVBoxLayout();
BinRadio = new QRadioButton(tr("Binomial"), this);
ChebyRadio = new QRadioButton(tr("Chebyshev"), this);
BinRadio->setChecked(true);
h7_box->addWidget(BinRadio);
h7_box->addWidget(ChebyRadio);
// Maximum ripple
QHBoxLayout *h8 = new QHBoxLayout();
maxRippleLabel = new QLabel(tr("Maximum ripple"));
h8->addWidget(maxRippleLabel);
MaxRippleEdit = new QLineEdit("0.05");
h8->addWidget(MaxRippleEdit);
MaxRippleEdit->setFixedWidth(50);
MaxRippleEdit->setAlignment(Qt::AlignLeft);
h8->setAlignment(Qt::AlignLeft);
QGridLayout *OptLayout = new QGridLayout();
v7_box->addLayout(h7_box);
v7_box->addLayout(h8);
Weighting_groupBox->setLayout(v7_box);
Weighting_groupBox->setVisible(false);
h7->addWidget(Weighting_groupBox);
MethodLayout->addLayout(h7);
MethodLayout->addLayout(h6);
MethodBox->setLayout(MethodLayout);
BalancedCheck = new QCheckBox(tr("Use balanced stubs"));
BalancedCheck->setEnabled(false);
TwoCheck = new QCheckBox(tr("Calculate two-port matching"));
TwoCheck->setChecked(true);
AddSPBlock =
new QCheckBox(tr("Add S-Parameter simulation")); // The user can choose
// whether add a S-param
// simulation or not
AddSPBlock->setChecked(false);
MicrostripCheck = new QCheckBox(tr("Synthesize microstrip lines"));
MicrostripCheck->setEnabled(false);
MicrostripCheck->setChecked(false);
OptLayout->addWidget(BalancedCheck, 0, 0);
OptLayout->addWidget(TwoCheck, 0, 1);
OptLayout->addWidget(AddSPBlock, 1, 0);
OptLayout->addWidget(MicrostripCheck, 1, 1);
matchFrame->addLayout(OptLayout);
connect(TwoCheck, SIGNAL(toggled(bool)), SLOT(slotSetTwoPort(bool)));
connect(MicrostripCheck, SIGNAL(toggled(bool)),
SLOT(slotSetMicrostripCheck()));
connect(ChebyRadio, SIGNAL(toggled(bool)), SLOT(slotChebyCheck()));
// ...........................................................
QGroupBox *ImpBox = new QGroupBox(tr("Reference Impedance"));
matchFrame->addWidget(ImpBox);
QHBoxLayout *ImpLayout = new QHBoxLayout();
Port1Label = new QLabel(tr("Port 1"));
Ref1Edit = new QLineEdit("50");
Ref1Edit->setMaximumWidth(75);
Ref1Edit->setValidator(DoubleVal);
Ohm1Label = new QLabel(QString(QChar(0xA9, 0x03)));
connect(Ref1Edit, SIGNAL(textChanged(const QString &)),
SLOT(slotImpedanceChanged(const QString &)));
Port2Label = new QLabel(tr("Port 2"));
Ref2Edit = new QLineEdit("50");
Ref2Edit->setMaximumWidth(75);
Ref2Edit->setValidator(DoubleVal);
Ohm2Label = new QLabel(QString(QChar(0xA9, 0x03)));
ImpLayout->addWidget(Port1Label);
ImpLayout->addWidget(Ref1Edit);
ImpLayout->addWidget(Ohm1Label);
ImpLayout->addSpacing(50);
ImpLayout->addWidget(Port2Label);
ImpLayout->addWidget(Ref2Edit);
ImpLayout->addWidget(Ohm2Label);
ImpBox->setLayout(ImpLayout);
// ...........................................................
QGroupBox *SParBox = new QGroupBox(tr("S Parameter"));
matchFrame->addWidget(SParBox);
QVBoxLayout *SParLayout = new QVBoxLayout();
SParBox->setLayout(SParLayout);
QHBoxLayout *h1 = new QHBoxLayout();
h1->setSpacing(3);
FormatLabel = new QLabel(tr("Input format"));
h1->addWidget(FormatLabel);
FormatCombo = new QComboBox();
h1->addWidget(FormatCombo);
FormatCombo->setFixedWidth(140);
FormatCombo->addItem(tr("Real/Imag"));
FormatCombo->addItem(tr("mag/deg"));
connect(FormatCombo, SIGNAL(activated(int)), SLOT(slotChangeMode(int)));
h1->addStretch(5);
SParLayout->addLayout(h1);
QHBoxLayout *h3 = new QHBoxLayout();
h3->setSpacing(3);
QVBoxLayout *VBox1 = new QVBoxLayout();
h3->addLayout(VBox1);
S11Label = new QLabel(tr("S11"));
S21Label = new QLabel(tr("S21"));
VBox1->addWidget(S11Label);
VBox1->addWidget(S21Label);
QVBoxLayout *VBox2 = new QVBoxLayout();
h3->addLayout(VBox2);
S11magEdit = new QLineEdit("0.5");
S11magEdit->setValidator(DoubleVal);
S11magEdit->setMaximumWidth(75);
S21magEdit = new QLineEdit("0.5");
S21magEdit->setValidator(DoubleVal);
S21magEdit->setMaximumWidth(75);
VBox2->addWidget(S11magEdit);
VBox2->addWidget(S21magEdit);
QVBoxLayout *VBox3 = new QVBoxLayout();
h3->addLayout(VBox3);
S11sLabel = new QLabel("+j");
S21sLabel = new QLabel("+j");
VBox3->addWidget(S11sLabel);
VBox3->addWidget(S21sLabel);
QVBoxLayout *VBox4 = new QVBoxLayout();
h3->addLayout(VBox4);
S11degEdit = new QLineEdit("0");
S11degEdit->setValidator(DoubleVal);
S11degEdit->setMaximumWidth(75);
S21degEdit = new QLineEdit("0");
S21degEdit->setValidator(DoubleVal);
S21degEdit->setMaximumWidth(75);
VBox4->addWidget(S11degEdit);
VBox4->addWidget(S21degEdit);
QVBoxLayout *VBox5 = new QVBoxLayout();
h3->addLayout(VBox5);
S11uLabel = new QLabel(" ");
S21uLabel = new QLabel(" ");
VBox5->addWidget(S11uLabel);
VBox5->addWidget(S21uLabel);
h3->addStretch(5);
QVBoxLayout *VBox6 = new QVBoxLayout();
h3->addLayout(VBox6);
S12Label = new QLabel(tr("S12"));
S22Label = new QLabel(tr("S22"));
VBox6->addWidget(S12Label);
VBox6->addWidget(S22Label);
QVBoxLayout *VBox7 = new QVBoxLayout();
h3->addLayout(VBox7);
S12magEdit = new QLineEdit("0");
S12magEdit->setValidator(DoubleVal);
S12magEdit->setMaximumWidth(75);
S22magEdit = new QLineEdit("0.5");
S22magEdit->setValidator(DoubleVal);
S22magEdit->setMaximumWidth(75);
VBox7->addWidget(S12magEdit);
VBox7->addWidget(S22magEdit);
QVBoxLayout *VBox8 = new QVBoxLayout();
h3->addLayout(VBox8);
S12sLabel = new QLabel("+j");
S22sLabel = new QLabel("+j");
VBox8->addWidget(S12sLabel);
VBox8->addWidget(S22sLabel);
QVBoxLayout *VBox9 = new QVBoxLayout();
h3->addLayout(VBox9);
S12degEdit = new QLineEdit("0");
S12degEdit->setMaximumWidth(75);
S12degEdit->setValidator(DoubleVal);
S22degEdit = new QLineEdit("0");
S22degEdit->setMaximumWidth(75);
S22degEdit->setValidator(DoubleVal);
VBox9->addWidget(S12degEdit);
VBox9->addWidget(S22degEdit);
QVBoxLayout *VBox0 = new QVBoxLayout();
h3->addLayout(VBox0);
S12uLabel = new QLabel(" ");
S22uLabel = new QLabel(" ");
VBox0->addWidget(S12uLabel);
VBox0->addWidget(S22uLabel);
SParLayout->addLayout(h3);
// set tab order to a more natural mode
setTabOrder(S11magEdit, S11degEdit);
setTabOrder(S11degEdit, S12magEdit);
setTabOrder(S12magEdit, S12degEdit);
setTabOrder(S12degEdit, S21magEdit);
setTabOrder(S21magEdit, S21degEdit);
setTabOrder(S21degEdit, S22magEdit);
setTabOrder(S22magEdit, S22degEdit);
connect(S21magEdit, SIGNAL(textChanged(const QString &)),
SLOT(slotImpedanceChanged(const QString &)));
connect(S21degEdit, SIGNAL(textChanged(const QString &)),
SLOT(slotImpedanceChanged(const QString &)));
connect(S11magEdit, SIGNAL(textChanged(const QString &)),
SLOT(slotReflexionChanged(const QString &)));
connect(S11degEdit, SIGNAL(textChanged(const QString &)),
SLOT(slotReflexionChanged(const QString &)));
QHBoxLayout *h2 = new QHBoxLayout();
h2->setSpacing(3);
FrequencyLabel = new QLabel(tr("Frequency:"));
FrequencyEdit = new QLineEdit();
FrequencyEdit->setValidator(DoubleVal);
FrequencyEdit->setMaximumWidth(75);
h2->addWidget(FrequencyLabel);
h2->addWidget(FrequencyEdit);
UnitCombo = new QComboBox();
UnitCombo->addItem("Hz");
UnitCombo->addItem("kHz");
UnitCombo->addItem("MHz");
UnitCombo->addItem("GHz");
UnitCombo->setFixedWidth(100);
h2->addWidget(UnitCombo);
h2->addStretch(5);
SParLayout->addLayout(h2);
// ...........................................................
QHBoxLayout *h0 = new QHBoxLayout();
h0->setSpacing(5);
matchFrame->addLayout(h0);
h0->addStretch(5);
QPushButton *buttCreate = new QPushButton(tr("Create"));
QPushButton *buttCancel = new QPushButton(tr("Cancel"));
h0->addWidget(buttCreate);
h0->addWidget(buttCancel);
connect(buttCreate, SIGNAL(clicked()), SLOT(slotButtCreate()));
connect(buttCancel, SIGNAL(clicked()), SLOT(reject()));
slotReflexionChanged(""); // calculate impedance
setFrequency(1e9); // set 1GHz
}
MatchDialog::~MatchDialog() {
delete all;
delete DoubleVal;
}
// -----------------------------------------------------------------------
void MatchDialog::setFrequency(double Freq_) {
int Expo = int(log10(Freq_) / 3.0);
if (Expo < 0)
Expo = 0;
else if (Expo > 3)
Expo = 3;
UnitCombo->setCurrentIndex(Expo);
Freq_ /= pow(10.0, double(3 * Expo));
FrequencyEdit->setText(QString::number(Freq_));
}
//------------------------------------------------------
// This function sets the visibility of the microstrip synthesis panel. The
// substrate properties are visible when the microstrip implementation is
// selected.
void MatchDialog::slotSetMicrostripCheck() {
if (MicrostripCheck->isChecked()) {
SubstrateBox->setVisible(true);
resize(650, 100);
} else {
SubstrateBox->setVisible(false);
setMaximumSize(500, 100);
}
}
//---------------------------------------------------------
// When the Chebyshev weighting is selected, the window show the MaxRipple
// textbox and label so as to let the user to enter this parameter. It makes no
// sense to show it otherwise since when using the binomial weighting, the
// maximum ripple is given by the number of sections
void MatchDialog::slotChebyCheck() {
if (ChebyRadio->isChecked()) {
MaxRippleEdit->setVisible(true);
maxRippleLabel->setVisible(true);
} else {
MaxRippleEdit->setVisible(false);
maxRippleLabel->setVisible(false);
}
}
// Set visibility of LineEdits and Labels associated with two-port matching
void MatchDialog::set2PortWidgetsVisible(bool visible) {
S12magEdit->setVisible(visible);
S22magEdit->setVisible(visible);
S12degEdit->setVisible(visible);
S22degEdit->setVisible(visible);
S12Label->setVisible(visible);
S22Label->setVisible(visible);
S12sLabel->setVisible(visible);
S22sLabel->setVisible(visible);
S12degEdit->setVisible(visible);
S22degEdit->setVisible(visible);
S12uLabel->setVisible(visible);
S22uLabel->setVisible(visible);
Port2Label->setVisible(visible);
Ref2Edit->setVisible(visible);
Ohm2Label->setVisible(visible);
}
// -----------------------------------------------------------------------
// Is called when the checkbox for two-port matching changes.
void MatchDialog::slotSetTwoPort(bool on) {
if (on) { // two-port matching ?
S11Label->setText(tr("S11"));
S21Label->setText(tr("S21"));
// restore the previous S21 values
setS21LineEdits(tmpS21mag, tmpS21deg);
set2PortWidgetsVisible(true);
} else {
S11Label->setText(tr("Reflexion Coefficient"));
S21Label->setText(tr("Impedance (Ohms)"));
set2PortWidgetsVisible(false);
// save S21 values, as these will be overwritten with the impedance value
tmpS21mag = S21magEdit->text().toDouble();
tmpS21deg = S21degEdit->text().toDouble();
slotReflexionChanged(""); // calculate impedance
}
}
//------------------------------------------------------------------------
// This function is called when a new topology is selected. According to the
// index selected, it makes visible (or invisible) certain window components.
void MatchDialog::slotChangeMode_TopoCombo() {
if ((TopoCombo->currentIndex() == 1) ||
(TopoCombo->currentIndex() == 2)) // Single/Double stub selected
{
ShortRadioButton->setVisible(true);
OpenRadioButton->setVisible(true);
OpenRadioButton->setChecked(true);
BalancedCheck->setEnabled(true);
} else {
ShortRadioButton->setVisible(false);
OpenRadioButton->setVisible(false);
BalancedCheck->setEnabled(false);
}
if ((TopoCombo->currentIndex() != 0) &&
(TopoCombo->currentIndex() != 4)) // Obviously, the microstrip
// implementation panel cannot be used
// when LC is selected
{
MicrostripCheck->setEnabled(true);
} else {
MicrostripCheck->setEnabled(false);
}
if (TopoCombo->currentIndex() == 3)
Weighting_groupBox->setVisible(true); // Cascaded lambda/4 sections selected
else
Weighting_groupBox->setVisible(false);
if ((TopoCombo->currentIndex() == 4) ||
(TopoCombo->currentIndex() == 3)) // Cascaded LC sections selected
{
OrderLabel->setVisible(true);
OrderEdit->setVisible(true);
} else {
OrderLabel->setVisible(false);
OrderEdit->setVisible(false);
}
}
// -----------------------------------------------------------------------
// Is called when the combobox changes between mag/deg and real/imag.
void MatchDialog::slotChangeMode(int Index) {
if (Index) { // polar ?
S11sLabel->setText("/");
S12sLabel->setText("/");
S21sLabel->setText("/");
S22sLabel->setText("/");
S11uLabel->setText(QString::fromUtf8("°"));
S12uLabel->setText(QString::fromUtf8("°"));
S21uLabel->setText(QString::fromUtf8("°"));
S22uLabel->setText(QString::fromUtf8("°"));
double Real = S11magEdit->text().toDouble();
double Imag = S11degEdit->text().toDouble();
c2p(Real, Imag);
setS11LineEdits(Real, Imag);
Real = S12magEdit->text().toDouble();
Imag = S12degEdit->text().toDouble();
c2p(Real, Imag);
setS12LineEdits(Real, Imag);
Real = S21magEdit->text().toDouble();
Imag = S21degEdit->text().toDouble();
c2p(Real, Imag);
setS21LineEdits(Real, Imag);
// convert also temp entries for future use
c2p(tmpS21mag, tmpS21deg);
Real = S22magEdit->text().toDouble();
Imag = S22degEdit->text().toDouble();
c2p(Real, Imag);
setS22LineEdits(Real, Imag);
} else { // cartesian
S11sLabel->setText("+j");
S12sLabel->setText("+j");
S21sLabel->setText("+j");
S22sLabel->setText("+j");
S11uLabel->setText(" ");
S12uLabel->setText(" ");
S21uLabel->setText(" ");
S22uLabel->setText(" ");
double Mag = S11magEdit->text().toDouble();
double Phase = S11degEdit->text().toDouble();
p2c(Mag, Phase);
setS11LineEdits(Mag, Phase);
Mag = S12magEdit->text().toDouble();
Phase = S12degEdit->text().toDouble();
p2c(Mag, Phase);
setS12LineEdits(Mag, Phase);
Mag = S21magEdit->text().toDouble();
Phase = S21degEdit->text().toDouble();
p2c(Mag, Phase);
setS21LineEdits(Mag, Phase);
// convert also temp entries for future use
p2c(tmpS21mag, tmpS21deg);
Mag = S22magEdit->text().toDouble();
Phase = S22degEdit->text().toDouble();
p2c(Mag, Phase);
setS22LineEdits(Mag, Phase);
}
}
// -----------------------------------------------------------------------
// Is called if the user changed the impedance. -> The reflexion
// coefficient is calculated.
void MatchDialog::slotImpedanceChanged(const QString &) {
if (TwoCheck->isChecked())
return;
double Z0 = Ref1Edit->text().toDouble();
double Real = S21magEdit->text().toDouble();
double Imag = S21degEdit->text().toDouble();
if (FormatCombo->currentIndex()) { // entries in polar format
p2c(Real, Imag);
z2r(Real, Imag, Z0);
c2p(Real, Imag);
} else {
z2r(Real, Imag, Z0);
}
setS11LineEdits(Real, Imag);
}
// -----------------------------------------------------------------------
// Is called if the user changed the reflexion coefficient. -> The impedance
// is calculated.
void MatchDialog::slotReflexionChanged(const QString &) {
if (TwoCheck->isChecked())
return;
double Z0 = Ref1Edit->text().toDouble();
double Real = S11magEdit->text().toDouble();
double Imag = S11degEdit->text().toDouble();
if (FormatCombo->currentIndex()) { // entries in polar format
p2c(Real, Imag);
r2z(Real, Imag, Z0);
c2p(Real, Imag);
} else {
r2z(Real, Imag, Z0);
}
setS21LineEdits(Real, Imag);
}
void MatchDialog::setS11LineEdits(double Real, double Imag) {
S11magEdit->blockSignals(true); // do not call slot for "textChanged"
S11magEdit->setText(QString::number(Real));
S11magEdit->blockSignals(false);
S11degEdit->blockSignals(true); // do not call slot for "textChanged"
S11degEdit->setText(QString::number(Imag));
S11degEdit->blockSignals(false);
}
void MatchDialog::setS12LineEdits(double Real, double Imag) {
S12magEdit->setText(QString::number(Real));
S12degEdit->setText(QString::number(Imag));
}
void MatchDialog::setS21LineEdits(double Real, double Imag) {
S21magEdit->blockSignals(true); // do not call slot for "textChanged"
S21magEdit->setText(QString::number(Real));
S21magEdit->blockSignals(false);
S21degEdit->blockSignals(true); // do not call slot for "textChanged"
S21degEdit->setText(QString::number(Imag));
S21degEdit->blockSignals(false);
}
void MatchDialog::setS22LineEdits(double Real, double Imag) {
S22magEdit->setText(QString::number(Real));
S22degEdit->setText(QString::number(Imag));
}
// -----------------------------------------------------------------------
// Is called if the "Create"-button is pressed.
void MatchDialog::slotButtCreate() {
double Z1 = Ref1Edit->text().toDouble(); // Port 1 impedance
double Z2 = Ref2Edit->text().toDouble(); // Port 2 impedance
double Freq = FrequencyEdit->text().toDouble() *
pow(10.0, 3.0 * UnitCombo->currentIndex());
// S matrix
double S11real = S11magEdit->text().toDouble();
double S11imag = S11degEdit->text().toDouble();
double S12real = S12magEdit->text().toDouble();
double S12imag = S12degEdit->text().toDouble();
double S21real = S21magEdit->text().toDouble();
double S21imag = S21degEdit->text().toDouble();
double S22real = S22magEdit->text().toDouble();
double S22imag = S22degEdit->text().toDouble();
if (FormatCombo->currentIndex()) { // are they polar ?
p2c(S11real, S11imag);
p2c(S12real, S12imag);
p2c(S21real, S21imag);
p2c(S22real, S22imag);
}
bool BalancedStubs = BalancedCheck->isChecked();
bool micro_syn = MicrostripCheck->isChecked(); // Microstrip implementation?
bool SP_block = AddSPBlock->isChecked(); // Add S-parameter block?
bool open_short =
OpenRadioButton
->isChecked(); // Open stub or short circuit stub configuration
int order = OrderEdit->text().toInt() +
1; // Order of the multisection lambda/4 matching
bool success = true;
double gamma_MAX =
MaxRippleEdit->text()
.toDouble(); // Maximum ripple (Chebyshev weighting only)
tSubstrate Substrate;
if (micro_syn) // In case microstrip implementation is selected. This reads
// the substrate properties given by the user
{
Substrate.er = RelPermCombo->currentText().section(" ", 0, 0).toDouble();
Substrate.height = SubHeightEdit->text().toDouble() / 1e3;
Substrate.thickness = thicknessEdit->text().toDouble() / 1e6;
Substrate.tand = tanDEdit->text().toDouble();
Substrate.resistivity = ResistivityEdit->text().toDouble();
Substrate.roughness = 0.0;
Substrate.minWidth = minWEdit->text().toDouble() / 1e3;
Substrate.maxWidth = maxWEdit->text().toDouble() / 1e3;
}
if (TwoCheck->isChecked()) { // two-port matching ?
// determinant of S-parameter matrix
double DetReal = S11real * S22real - S11imag * S22imag - S12real * S21real +
S12imag * S21imag;
double DetImag = S11real * S22imag + S11imag * S22real - S12real * S21imag -
S12imag * S21real;
// Check unconditional stability. If the device is not unconditionally stable, it cannot be conjugately matched
std::complex<double> s11 (S11real, S11imag);
std::complex<double> s12 (S12real, S12imag);
std::complex<double> s21 (S21real, S21imag);
std::complex<double> s22 (S22real, S22imag);
double delta = abs(s11*s22 - s12*s21); // Determinant of the S matrix
double K = (1 - abs(s11)*abs(s11) - abs(s22)*abs(s22) + delta*delta) / (2*abs(s12*s21)); // Rollet factor.
if ((K > 1) && (delta < 1)){
// The device is unconditionally stable. It can be conjugately matched
success =
calc2PortMatch(S11real, S11imag, S22real, S22imag, DetReal, DetImag, Z1,
Z2, Freq, micro_syn, SP_block, open_short, Substrate,
order, gamma_MAX, BalancedStubs);
}else{
// The device is not unconditionally stable. Show a message and stop
success = false;
QMessageBox::critical(
0, tr("Error"),
tr("The device is not unconditionally stable:\n\nK = %1\n|%2| = %3\n\nIt is not possible to synthesize a matching network.\n\nConsider adding resistive losses and/or feedback to reach unconditional stability (K > 1 and |%2| < 1)")
.arg(QString::number(K, 'f', 2)).arg(QChar(0x0394)).arg(QString::number(delta, 'f', 2)));
}
} else {
success = calcMatchingCircuit(S11real, S11imag, Z1, Freq, micro_syn,
SP_block, open_short, Substrate, order,
gamma_MAX, BalancedStubs);
}
if (!success) {
// Something went wrong. Return to the main window without closing the dialog
return;
}
QucsMain->slotEditPaste(success);
accept();
}
// -----------------------------------------------------------------------
// transform real/imag into mag/deg (cartesian to polar)
void MatchDialog::c2p(double &Real, double &Imag) {
double Real_ = Real;
Real = sqrt(Real * Real + Imag * Imag); // magnitude
Imag = 180.0 / pi * atan2(Imag, Real_); // phase in degree
}
// -----------------------------------------------------------------------
// transform mag/deg into real/imag (polar to cartesian)
void MatchDialog::p2c(double &Real, double &Imag) {
double Real_ = Real;
Real = Real_ * cos(Imag * pi / 180.0); // real part
Imag = Real_ * sin(Imag * pi / 180.0); // imaginary part
}
// -----------------------------------------------------------------------
// transform reflexion coefficient into impedance
void MatchDialog::r2z(double &Real, double &Imag, double Z0) {
double tmp = Z0 / ((1.0 - Real) * (1.0 - Real) + Imag * Imag);
Real = (1.0 - Real * Real - Imag * Imag) * tmp;
Imag *= 2.0 * tmp;
}
// -----------------------------------------------------------------------
// transform impedance into reflexion coefficient
void MatchDialog::z2r(double &Real, double &Imag, double Z0) {
double tmp = (Real + Z0) * (Real + Z0) + Imag * Imag;
Real = (Real * Real + Imag * Imag - Z0 * Z0) / tmp;
Imag *= 2.0 * Z0 / tmp;
}
// -----------------------------------------------------------------------
// This function calculates a LC matching section
QString MatchDialog::calcMatchingLC(double r_real, double r_imag, double Z0,
double Freq) {
double Zreal = r_real, Zimag = r_imag;
r2z(Zreal, Zimag, Z0);
if (Zreal < 0.0) {
if (Zreal < -1e-13) {
QMessageBox::critical(
0, tr("Error"),
tr("Real part of impedance must be greater zero,\nbut is %1 !")
.arg(Zreal));
return QString(); // matching not possible
}
// In high-Q circuits, Zreal often becomes somewhat about -1e-16
// because of numerical inaccuracy.
Zreal = 0.0;
}
double X1, X2, Omega = 2.0 * pi * Freq;
QString Str;
if (r_real < 0) {
// ...................................................
// first serial than parallel component (possible if Zreal <= Z0)
Str = "sp";
X1 = sqrt(Zreal * (Z0 - Zreal));
if (Zimag < 0.0)
X1 *= -1.0; // always use shortest matching path
X1 -= Zimag;
// parallel component
X2 = (Zimag + X1) / (Zreal * Zreal + (Zimag + X1) * (Zimag + X1));
} else {
Str = "ps";
X1 = Zreal + Zimag * Zimag / Zreal - Z0;
// ...................................................
// first parallel than serial component (possible if X >= 0.0)
X1 = sqrt(Z0 * X1);
if (Zimag > 0.0)
X1 *= -1.0; // always use shortest matching path
// parallel component
X2 = Zimag / (Zreal * Zreal + Zimag * Zimag) + X1 / (Z0 * Z0 + X1 * X1);
}
// Circuit topology and values
QString laddercode = "", series_element, shunt_element;
// serial component
if (X1 < 0.0) // capacitance ?
series_element = QStringLiteral("CS:%1;").arg(-1.0 / Omega / X1);
else // inductance
series_element = QStringLiteral("LS:%1;").arg(X1 / Omega);
// parallel component
if (X2 < 0.0) // inductance ?
shunt_element = QStringLiteral("LP:%1;").arg(-1.0 / Omega / X2);
else // capacitance
shunt_element = QStringLiteral("CP:%1;").arg(X2 / Omega);
(r_real < 0) ? laddercode = shunt_element + series_element
: laddercode = series_element + shunt_element;
return laddercode;
}
// -----------------------------------------------------------------------
// This function calls the specific matching network function so as to get the
// desired matching topology Returns true if the synthesis worked as expected or
// false if it wasn't
bool MatchDialog::calcMatchingCircuit(double S11real, double S11imag, double Z0,
double Freq, bool micro_syn,
bool SP_Block, bool open_short,
tSubstrate Substrate, int order,
double gamma_MAX, bool BalancedStubs) {
QString laddercode;
switch (TopoCombo->currentIndex()) {
case 0: // LC
laddercode = calcMatchingLC(S11real, S11imag, Z0,
Freq); // It calculates the LC matching circuit.
break;
case 1: // Single stub
laddercode =
calcSingleStub(S11real, S11imag, Z0, Freq, open_short, BalancedStubs);
break;
case 2: // Double stub
laddercode =
calcDoubleStub(S11real, S11imag, Z0, Freq, open_short, BalancedStubs);
break;
case 3: // Quarter wave cascaded sections
(BinRadio->isChecked())
? laddercode = calcBinomialLines(S11real, S11imag, Z0, order, Freq)
: laddercode =
calcChebyLines(S11real, S11imag, Z0, gamma_MAX, order, Freq);
break;
case 4: // Cascaded LC sections
laddercode =
calcMatchingCascadedLCSections(S11real, S11imag, Z0, Freq, order - 1);
break;
case 5: // Lambda/8 + Lambda/4 impedance transformer
// Reference: Inder J. Bahl. "Fundamentals of RF and microwave
// transistor amplifiers". John Wiley and Sons. 2009. Pages 159-160
laddercode = calcMatchingLambda8Lambda4(S11real, S11imag, Z0, Freq);
break;
}
if (laddercode.isEmpty())
return false;
double RL = S11real, XL = S11imag;
QString wirestr = "";
QString componentstr = "";
QString paintingstr = "";
int x_pos = 0;
r2z(RL, XL, Z0);
if (SP_Block) {
laddercode.append("S2P:Freq;");
laddercode.prepend(QStringLiteral("P1:%1;").arg(Z0));
laddercode.append(QStringLiteral("ZL:%1#%2;").arg(RL).arg(XL));
} else // Add tags instead of a S-param simulation
{
laddercode.prepend("LBL:Port 1;");
laddercode.append("LBL:Port 2;");
}
SchematicParser(laddercode, x_pos, Freq, Substrate, micro_syn);
return true; // The schematic was successfully created
}
// -----------------------------------------------------------------------
// Fundamental calculations for concurrent 2-port matching.
QString MatchDialog::calcBiMatch(double S11real, double S11imag, double S22real,
double S22imag, double DetReal, double DetImag,
double Z0, double Freq, bool open_short,
double gamma_MAX, int order,
bool BalancedStubs) {
double B = 1.0 + S11real * S11real + S11imag * S11imag - S22real * S22real -
S22imag * S22imag - DetReal * DetReal - DetImag * DetImag;
double Creal = S11real - S22real * DetReal - S22imag * DetImag;
double Cimag = S22real * DetImag - S11imag - S22imag * DetReal;
double Cmag = 2.0 * (Creal * Creal + Cimag * Cimag);
Creal /= Cmag;
Cimag /= Cmag;
double Rreal = B * B - 2.0 * Cmag;
double Rimag;
if (Rreal < 0.0) {
Rimag = Cimag * B - Creal * sqrt(-Rreal);
Rreal = Creal * B + Cimag * sqrt(-Rreal);
} else {
Rreal = B - sqrt(Rreal);
Rimag = Cimag * Rreal;
Rreal *= Creal;
}
QString laddercode;
switch (TopoCombo->currentIndex()) // Matches both the input and the output
// port to external sources (typically, 50
// Ohms)
{
case 0: // LC
laddercode = calcMatchingLC(Rreal, -Rimag, Z0, Freq);
break;
case 1: // Single stub
laddercode =
calcSingleStub(Rreal, -Rimag, Z0, Freq, open_short, BalancedStubs);
break;
case 2: // Double stub
laddercode =
calcDoubleStub(Rreal, -Rimag, Z0, Freq, open_short, BalancedStubs);
break;
case 3: // Quarter wave cascaded sections
(BinRadio->isChecked())
? laddercode = calcBinomialLines(Rreal, -Rimag, Z0, order, Freq)
: laddercode =
calcChebyLines(Rreal, -Rimag, Z0, gamma_MAX, order, Freq);
break;
case 4: // Cascaded LC sections
laddercode = calcMatchingCascadedLCSections(Rreal, -Rimag, Z0, Freq, order);
break;
case 5: // Lambda/8 + Lambda/4 transformer
laddercode = calcMatchingLambda8Lambda4(Rreal, -Rimag, Z0, Freq);
break;
}
return laddercode;
}
// -----------------------------------------------------------------------
// This function calculates both the input and the output matching networks. In
// this sense, it calls 'calcBiMatch' function to get the input/output
// impedance. Then it synthesize the network according to the user choice
bool MatchDialog::calc2PortMatch(double S11real, double S11imag, double S22real,
double S22imag, double DetReal, double DetImag,
double Z1, double Z2, double Freq,
bool microsyn, bool SP_Block, bool open_short,
tSubstrate Substrate, int order,
double gamma_MAX, bool BalancedStubs) {
// Input port network
// The result is a string which gives the structure of the matching network
QString InputLadderCode =
calcBiMatch(S11real, S11imag, S22real, S22imag, DetReal, DetImag, Z1,
Freq, open_short, gamma_MAX, order, BalancedStubs);
if (InputLadderCode.isEmpty())
return false; // Synthesis error
// Output port network
QString OutputLadderCode =
calcBiMatch(S22real, S22imag, S11real, S11imag, DetReal, DetImag, Z2,
Freq, open_short, gamma_MAX, order, BalancedStubs);
if (OutputLadderCode.isEmpty())
return false; // Synthesis error
else
OutputLadderCode = flipLadderCode(OutputLadderCode);
if (SP_Block) {
InputLadderCode.prepend(QStringLiteral("S2P:%1;").arg(
Freq)); // Add the s-param simulation to the circuit code
InputLadderCode.prepend(QStringLiteral("P1:%1;").arg(Z1)); // Port 1
OutputLadderCode.append(QStringLiteral("P2:%1;").arg(Z2)); // Port 2
} else { // Use labels instead of the S-param simulation
InputLadderCode.prepend(QStringLiteral("LBL:Port 1;")); // Port 1
OutputLadderCode.append(QStringLiteral("LBL:Port 2;")); // Port 2
}
QString laddercode = InputLadderCode + QStringLiteral("DEV:0") + OutputLadderCode;
int x_pos = 0;
SchematicParser(laddercode, x_pos, Freq, Substrate, microsyn);
return true;
}
// This function flips the ladder structure so as to correctly process the
// output matching network
QString MatchDialog::flipLadderCode(QString laddercode) {
QStringList strlist = laddercode.split(";"), auxlist;
std::reverse(strlist.begin(), strlist.end());
QString flipped_laddercode = "";
// Build the flipped ladder code
for (int i = 0; i < strlist.length(); i++) {
flipped_laddercode += strlist.at(i) + ";";
}
return flipped_laddercode;
}
// FUNCTIONS FOR THE MICROSTRIP LINE SYNTHESIS. JUST COPIED FROM THE QUCS-FILTER
// TOOL
/////////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_ERROR 1e-7
void calcMicrostrip(tSubstrate *substrate, double width, double freq,
double &er_eff, double &zl) {
double a, b;
double h = substrate->height;
double t = substrate->thickness;
double er = substrate->er;
double Wh = width / h;
t /= h;
// quasi-static models by Hammerstad
double w1 = Wh;
if (t > 1e-100) { // width correction due to metal thickness?
a = coth(sqrt(6.517 * Wh));
b = t / pi * log(1.0 + 10.873127 / t / a / a);
w1 += b;
Wh += 0.5 * b * (1.0 + sech(sqrt(er - 1.0)));
}
// relative effective permittivity
a = Wh * Wh;
b = a * a;
er_eff = -0.564 * pow((er - 0.9) / (er + 3.0), 0.053);
er_eff *= 1.0 + log((b + a / 2704.0) / (b + 0.432)) / 49.0 +
log(1.0 + a * Wh / 5929.741) / 18.7;
er_eff = (er + 1.0) / 2.0 + (er - 1.0) / 2.0 * pow(1.0 + 10.0 / Wh, er_eff);
// characteristic impedance
zl = 6.0 + 0.2831853 * exp(-pow(30.666 / Wh, 0.7528));
zl = Z_FIELD / 2.0 / pi * log(zl / Wh + sqrt(1.0 + 4.0 / Wh / Wh));
// characteristic impedance (same again for "w1")
a = 6.0 + 0.2831853 * exp(-pow(30.666 / w1, 0.7528));
a = Z_FIELD / 2.0 / pi * log(a / w1 + sqrt(1.0 + 4.0 / w1 / w1));
a /= zl;
zl /= sqrt(er_eff);
er_eff *= a * a;
// dispersion models by Kirschning
freq *= h / 1e6; // normalize frequency into GHz*mm
// relative effective permittivity
a = 0.0363 * exp(-4.6 * Wh) * (1.0 - exp(-pow(freq / 38.7, 4.97)));
a *= 1.0 + 2.751 * (1.0 - exp(-pow(er / 15.916, 8.0)));
a = pow((0.1844 + a) * freq, 1.5763);
a *= 0.27488 + Wh * (0.6315 + 0.525 / pow(1.0 + 0.0157 * freq, 20.0)) -
0.065683 * exp(-8.7513 * Wh);
a *= 0.33622 * (1.0 - exp(-0.03442 * er));
double er_freq = er - (er - er_eff) / (1.0 + a);
// characteristic impedance
a = -0.03891 * pow(er, 1.4);
b = -0.267 * pow(Wh, 7.0);
double R7 = 1.206 - 0.3144 * exp(a) * (1.0 - exp(b));
a = 0.016 + pow(0.0514 * er, 4.524);
b = pow(freq / 28.843, 12.0);
a = 5.086 * a * b / (0.3838 + 0.386 * a) / (1.0 + 1.2992 * b);
b = -22.2 * pow(Wh, 1.92);
a *= exp(b);
b = pow(er - 1.0, 6.0);
double R9 = a * b / (1.0 + 10.0 * b);
a = 4.766 * exp(-3.228 * pow(Wh, 0.641)); // = R3
a = 1.0 + 1.275 * (1.0 - exp(-0.004625 * a * pow(er, 1.674) *
pow(freq / 18.365, 2.745))); // = R8
b = 0.9408 * pow(er_freq, a) - 0.9603; // = R13
b /= (0.9408 - R9) * pow(er_eff, a) - 0.9603;
R9 = b; // = R13 / R14
a = 0.00044 * pow(er, 2.136) + 0.0184; // = R10
a *= 0.707 * pow(freq / 12.3, 1.097); // = R15
a = exp(-0.026 * pow(freq, 1.15656) - a);
b = pow(freq / 19.47, 6.0);
b /= 1.0 + 0.0962 * b; // = R11
b = 1.0 + 0.0503 * er * er * b * (1.0 - exp(-pow(Wh / 15, 6.0))); // = R16
R7 *= (1.0 - 1.1241 * a / b / (1.0 + 0.00245 * Wh * Wh)); // = R17
zl *= pow(R9, R7);
er_eff = er_freq;
}
// -------------------------------------------------------------------
// Calculates the width 'width' and the relative effective permittivity 'er_eff'
// of a microstrip line. It uses an iterative search algorithm because
// synthesis equations doesn't exist.
void MatchDialog::getMicrostrip(double Z0, double freq, tSubstrate *substrate,
double &width, double &er_eff) {
int iteration = 0; // iteration counter
double Z0_current, Z0_result, increment;
width = 1e-3; // start with 1mm
do {
// compute line parameters
calcMicrostrip(substrate, width, freq, er_eff, Z0_current);
if (fabs(Z0 - Z0_current) < MAX_ERROR)
break; // wanted value was found
increment = width / 100.0;
width += increment;
// compute line parameters
calcMicrostrip(substrate, width, freq, er_eff, Z0_result);
// Newton iteration: w(n+1) = w(n) - f(w(n))/f'(w(n))
// with f(w(n)) = Z0_current - Z0
// and f'(w(n)) = (Z0_result - Z0_current) / increment
width -= (Z0_current - Z0) / (Z0_result - Z0_current) * increment;
if (width <= 0.0)
width = increment;
iteration++;
} while (iteration < 150);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------
// Calculates a matching network according to the stub+line method
// Reference: 'Microwave Engineering'. David Pozar. John Wiley and Sons. 4th
// Edition. Pg 234-241
QString MatchDialog::calcSingleStub(double r_real, double r_imag, double Z0,
double Freq, bool open_short,
bool BalancedStubs) {
double t = 0, t1 = 0, t2 = 0;
double dl, dl1, dl2, B;
double B1, B2, d, lstub, ll;
double lambda = SPEED_OF_LIGHT / (Freq);
double RL = r_real, XL = r_imag;
r2z(RL, XL, Z0);
// Stub+line method formulas
if (RL == Z0) {
t = -XL / (2 * Z0);
(t < 0) ? dl = (pi + atan(t)) / (2 * pi) : dl = (atan(t)) / (2 * pi);
B = (RL * RL * t - (Z0 - XL * t) * (Z0 * t + XL)) /
(Z0 * (RL * RL + (Z0 * t + XL) * (Z0 * t + XL)));
} else {
t1 = (XL + sqrt(((RL / Z0) * fabs((Z0 - RL) * (Z0 - RL) + XL * XL)))) /
(RL - Z0);
(t1 < 0) ? dl1 = (pi + atan(t1)) / (2 * pi) : dl1 = (atan(t1)) / (2 * pi);
B1 = (RL * RL * t1 - (Z0 - XL * t1) * (Z0 * t1 + XL)) /
(Z0 * (RL * RL + (Z0 * t1 + XL) * (Z0 * t1 + XL)));
t2 = (XL - sqrt((RL * fabs((Z0 - RL) * (Z0 - RL) + XL * XL)) / (Z0))) /
(RL - Z0);
(t2 < 0) ? dl2 = (pi + atan(t2)) / (2 * pi) : dl2 = (atan(t2)) / (2 * pi);
B2 = (RL * RL * t2 - (Z0 - XL * t2) * (Z0 * t2 + XL)) /
(Z0 * (RL * RL + (Z0 * t2 + XL) * (Z0 * t2 + XL)));
}
if (t != 0) {
d = dl * lambda;
(open_short) ? ll = -(atan(B * Z0)) / (2 * pi)
: ll = (atan(1. / (B * Z0))) / (2 * pi);
if ((open_short) && (ll < 0))
ll += 0.5;
if ((!open_short) && (ll > 0.5))
ll -= 0.5;
lstub = ll * lambda;
}
if (t1 != 0) {
d = dl1 * lambda;
(open_short) ? ll = -(atan(B1 * Z0)) / (2 * pi)
: ll = (atan(1. / (1. * B1 * Z0))) / (2 * pi);
if ((open_short) && (ll < 0))
ll += 0.5;
if ((!open_short) && (ll > 0.5))
ll -= 0.5;
lstub = ll * lambda;
} else {
if (t2 != 0) {
d = dl2 * lambda;
(open_short) ? ll = -(atan(B2 * Z0)) / (2 * pi)
: ll = (atan(1. / (1. * B2 * Z0))) / (2 * pi);
if ((open_short) && (ll < 0))
ll += 0.5;
if ((!open_short) && (ll > 0.5))
ll -= 0.5;
lstub = ll * lambda;
}
}
if (BalancedStubs) { // Balanced stub implementation
double K;
(open_short) ? K = 0.5 : K = 2;
lstub = (lambda / (2 * pi)) * atan(K * tan((2 * pi * lstub) / lambda));
if (lstub < 0)
lstub += 0.5 * lambda;
}
// String code
QString laddercode;
if ((open_short) && (!BalancedStubs))
laddercode = QStringLiteral("OL:%1#%2;TL:%1#%3;")
.arg(Z0)
.arg(lstub)
.arg(d); // Line + Open stub
if ((open_short) && (BalancedStubs))
laddercode = QStringLiteral("OU:%1#%2;OL:%1#%2;TL:%1#%3;")
.arg(Z0)
.arg(lstub)
.arg(d); // Open circuit balanced stubs
if ((!open_short) && (!BalancedStubs))
laddercode = QStringLiteral("SL:%1#%2;TL:%1#%3;")
.arg(Z0)
.arg(lstub)
.arg(d); // Line + Short circuited stub
if ((!open_short) && (BalancedStubs))
laddercode = QStringLiteral("SU:%1#%2;SL:%1#%2;TL:%1#%3;")
.arg(Z0)
.arg(lstub)
.arg(d); // Short circuited balanced stubs
return laddercode;
}
//--------------------------------------------------------------------------------
// Calculates a matching network according to the stub+line+stub method
// Reference: 'Microwave Engineering', David Pozar. John Wiley and Sons. 4th
// Edition. Pg 241-245
QString MatchDialog::calcDoubleStub(double r_real, double r_imag, double Z0,
double Freq, bool open_short,
bool BalancedStubs) {
double RL = r_real, XL = r_imag;
r2z(RL, XL, Z0);
double Y0 = 1. / Z0;
double GL = (1 / ((RL * RL) + (XL * XL))) * RL;
double BL = -(1 / ((RL * RL) + (XL * XL))) * XL;
double lambda = SPEED_OF_LIGHT / Freq;
double beta = (2 * pi) / lambda;
double d = lambda / 8;
double t = tan(beta * d);
double ll1, ll2;
// Double stub method formulas
if (GL > Y0 * ((1 + t * t) / (2 * t * t))) // Not every load can be match
// using the double stub technique.
{
QMessageBox::warning(0, tr("Error"),
tr("It is not possible to match this load using the double stub method"));
return QString();
}
// Stubs susceptance
double B11 = -BL + (Y0 + sqrt((1 + t * t) * GL * Y0 - GL * GL * t * t)) /
(t); // 1st solution
// double B12 = -BL + (Y0 - sqrt((1+t*t)*GL*Y0 - GL*GL*t*t))/(t); // 2nd
// solution
double B21 =
((Y0 * sqrt((1 + t * t) * GL * Y0 - GL * GL * t * t)) + GL * Y0) /
(GL * t); // 1st solution
// double B22 = ((-Y0*sqrt((1+t*t)*GL*Y0 - GL*GL*t*t)) + GL*Y0)/(GL*t);// 2nd
// solution
// Open circuit solution
(open_short) ? ll1 = (atan(B11 * Z0)) / (2 * pi)
: ll1 = -(atan(1. / (1. * B11 * Z0))) / (2 * pi);
(open_short) ? ll2 = (atan(B21 * Z0)) / (2 * pi)
: ll2 = -(atan(1. / (1. * B21 * Z0))) / (2 * pi);
if (ll1 < 0)
ll1 += 0.5;
if (ll2 < 0)
ll2 += 0.5;
if ((!open_short) && (ll1 > 0.5))
ll1 -= 0.5;
if ((!open_short) && (ll2 > 0.5))
ll2 -= 0.5;
double lstub1 = ll1 * lambda, lstub2 = ll2 * lambda;
if (BalancedStubs) { // Balanced stub implementation
double K;
(open_short) ? K = 0.5 : K = 2;
lstub1 = (lambda / (2 * pi)) * atan(K * tan((2 * pi * lstub1) / lambda));
lstub2 = (lambda / (2 * pi)) * atan(K * tan((2 * pi * lstub2) / lambda));
if (lstub1 < 0)
lstub1 += 0.5 * lambda;
if (lstub2 < 0)
lstub2 += 0.5 * lambda;
}
QString laddercode;
if ((open_short) && (BalancedStubs))
laddercode = QStringLiteral("OU:%1#%2;OL:%1#%2;TL:%1#%3;OU:%1#%4;OL:%1#%4;")
.arg(Z0)
.arg(lstub2)
.arg(d)
.arg(lstub1);
if ((open_short) && (!BalancedStubs))
laddercode = QStringLiteral("OL:%1#%2;TL:%1#%3;OL:%1#%4;")
.arg(Z0)
.arg(lstub2)
.arg(d)
.arg(lstub1);
if ((!open_short) && (BalancedStubs))
laddercode = QStringLiteral("SU:%1#%2;SL:%1#%2;TL:%1#%3;SU:%1#%4;SL:%1#%4;")
.arg(Z0)
.arg(lstub2)
.arg(d)
.arg(lstub1);
if ((!open_short) && (!BalancedStubs))
laddercode = QStringLiteral("SL:%1#%2;TL:%1#%3;SL:%1#%4;")
.arg(Z0)
.arg(lstub2)
.arg(d)
.arg(lstub1);
return laddercode;
}
// -----------------------------------------------------------------------------------------------
// Binomial coefficients using the multiplicative formula (more efficient than
// the recursive one)
int BinomialCoeffs(int n, int k) {
double coeff = 1;
for (int i = 1; i <= k; i++) {
coeff *= (n + (1 - i)) / (1.0 * i);
}
return (int)coeff;
}
//-----------------------------------------------------------------------------------
// This function calculates a multistage lambda/4 matching using binomial
// weigthing Reference: 'Microwave Engineering'. David Pozar. John Wiley and
// Sons. 4th Edition. Pg 252-256
QString MatchDialog::calcBinomialLines(double r_real, double r_imag, double Z0,
int order, double Freq) {
double RL = r_real, XL = r_imag;
r2z(RL, XL, Z0);
if (RL == 0) {
QMessageBox::warning(
0, QObject::tr("Error"),
QObject::tr("The load has not resistive part. It cannot be matched "
"using the quarter wavelength method"));
return NULL;
}
if (XL != 0) {
QMessageBox::warning(0, QObject::tr("Warning"),
QObject::tr("Reactive loads cannot be matched. Only "
"the real part will be matched"));
}
double l4 = SPEED_OF_LIGHT / (4. * Freq);
double Ci, Zi, Zaux = Z0;
QString laddercode;
for (int i = 1; i < order; i++) {
Ci = BinomialCoeffs(order - 1, i - 1);
Zi = exp(log(Zaux) + (Ci / pow(2, order - 1)) * log(RL / Z0));
Zaux = Zi;
laddercode += QStringLiteral("TL:%1#%2;").arg(Zi).arg(l4);
}
return laddercode;
}
//-----------------------------------------------------------------------------------
// This function calculates a multistage lambda/4 matching using the Chebyshev
// weigthing. Reference 'Microwave Engineering'. David Pozar. John Wiley and
// Sons. 4th Edition. Pg 256-261
QString MatchDialog::calcChebyLines(double r_real, double r_imag, double Z0,
double gamma, int order, double Freq) {
const int N = order - 1; // Number of sections
if (N > 7) // So far, it is only available Chebyshev weighting up to 7
// sections. Probably, it makes no sense to use a higher number of
// sections because of the losses
{
QMessageBox::warning(
0, QObject::tr("Error"),
QObject::tr("Chebyshev weighting for N>7 is not available"));
return QString();
}
QString laddercode;
double RL = r_real, XL = r_imag;
r2z(RL, XL, Z0); // Calculation of the load impedance given the reflection coefficient
double sec_theta_m = 0.0;
double log_ratio = log(RL / Z0) / (2 * gamma);
if (fabs(log_ratio) < 1) {
sec_theta_m = 0.0;
} else {
sec_theta_m = cosh((1.0 / N) * acosh(fabs(log_ratio)));
}
std::vector<double> w(N,0.0);
switch (N) // The weights are calculated by equating the reflection coeffient
// formula to the N-th Chebyshev polinomial
{
case 1:
w[0] = sec_theta_m;
break;
case 2:
w[0] = sec_theta_m * sec_theta_m;
w[1] = 2 * (sec_theta_m * sec_theta_m - 1);
break;
case 3:
w[0] = pow(sec_theta_m, 3);
w[1] = 3 * (pow(sec_theta_m, 3) - sec_theta_m);
w[2] = w[1];
break;
case 4:
w[0] = pow(sec_theta_m, 4);
w[1] = 4 * sec_theta_m * sec_theta_m * (sec_theta_m * sec_theta_m - 1);
w[2] = 2 * (1 - 4 * sec_theta_m * sec_theta_m + 3 * pow(sec_theta_m, 4));
w[3] = w[1];
break;
case 5:
w[0] = pow(sec_theta_m, 5);
w[1] = 5 * (pow(sec_theta_m, 5) - pow(sec_theta_m, 3));
w[2] =
10 * pow(sec_theta_m, 5) - 15 * pow(sec_theta_m, 3) + 5 * sec_theta_m;
w[3] = w[2];
w[4] = w[1];
break;
case 6:
w[0] = pow(sec_theta_m, 6);
w[1] = 6 * pow(sec_theta_m, 4) * (sec_theta_m * sec_theta_m - 1);
w[2] = 15 * pow(sec_theta_m, 6) - 24 * pow(sec_theta_m, 4) +
9 * sec_theta_m * sec_theta_m;
w[3] = 2 * (10 * pow(sec_theta_m, 6) - 18 * pow(sec_theta_m, 4) +
9 * sec_theta_m * sec_theta_m - 1);
w[4] = w[2];
w[5] = w[1];
break;
case 7:
w[0] = pow(sec_theta_m, 7);
w[1] = 7 * pow(sec_theta_m, 5) * (sec_theta_m * sec_theta_m - 1);
w[2] = 21 * pow(sec_theta_m, 7) - 35 * pow(sec_theta_m, 5) +
14 * pow(sec_theta_m, 3);
w[3] = 35 * pow(sec_theta_m, 7) - 70 * pow(sec_theta_m, 5) +
42 * pow(sec_theta_m, 3) - 7 * sec_theta_m;
w[4] = w[3];
w[5] = w[2];
w[6] = w[1];
break;
}
double l4 = SPEED_OF_LIGHT / (4. * Freq);
double Zaux = Z0, Zi;
for (int i = 0; i < N; i++) {
(RL < Z0) ? Zi = exp(log(Zaux) - gamma * w[i])
: Zi = exp(log(Zaux) + gamma * w[i]); // When RL<Z0, Z_{i}<Z_{i-1}
Zaux = Zi;
laddercode += QStringLiteral("TL:%1#%2;").arg(Zi).arg(l4);
}
return laddercode;
}
//--------------------------------------------------------------------------
// It calculates a cascaded LC matching network (only real impedances)
// Reference: Inder J. Bahl. "Fundamentals of RF and microwave transistor
// amplifiers". John Wiley and Sons. 2009. Pages 169 - 170
QString MatchDialog::calcMatchingCascadedLCSections(double r_real,
double r_imag, double Z0,
double Freq, int N) {
double RL = r_real, XL = r_imag, RS = Z0;
double w = 2 * pi * Freq, Q, C, L;
r2z(RL, XL, Z0);
double Raux, R, R1, R2;
QString s = "";
if (RL == 0) {
QMessageBox::warning(
0, QObject::tr("Error"),
QObject::tr("The load is reactive. It cannot be matched "
"using the quarter wavelength method"));
return NULL;
}
if (XL != 0) {
QMessageBox::warning(0, QObject::tr("Warning"),
QObject::tr("Reactive loads cannot be matched. Only "
"the real part will be matched"));
}
if (RL > RS)//The design equations were tailored for the RS > RL case. In case of RL > RS, the ports impedance are swapped
R1 = RL, R2 = RS;
else
R1 = RS, R2 = RL;
Raux = R1;
for (int i = 0; i < N - 1; i++) {
R = pow(R1, 1. * (N - (i + 1)) / N) * pow(R2, 1. * (i + 1) / N);
Q = sqrt(Raux / R - 1);
C = Q / (w * Raux);
L = Q * R / w;
s += QStringLiteral("CP:%1;LS:%2;").arg(C).arg(L);
Raux = R;
}
Q = sqrt(R / R2 - 1);
C = Q / (w * R);
L = Q * R2 / w;
s += QStringLiteral("CP:%1;LS:%2;").arg(C).arg(L);
if (RL > RS) // Flip string
{
QString temp = "";
QStringList strlist = s.split(";");
for (int i = strlist.count() - 1; i >= 0; i--)
temp += strlist.at(i) + ";";
s = temp;
}
return s;
}
//--------------------------------------------------------------------------
// It calculates a lambda/8 + lambda/4 transformer to match a complex load to a
// real source
QString MatchDialog::calcMatchingLambda8Lambda4(double r_real, double r_imag,
double Z0, double Freq) {
double RL = r_real, XL = r_imag;
double l4 = SPEED_OF_LIGHT / (4. * Freq);
double l8 = .5 * l4;
r2z(RL, XL, Z0);
double Zmm = sqrt(RL * RL + XL * XL);
double Zm = sqrt((Z0 * RL * Zmm) / (Zmm - XL));
return QStringLiteral("TL:%1#%2;TL:%3#%4;").arg(Zm).arg(l4).arg(Zmm).arg(l8);
}
// Given a string code of inductors, capacitors and transmission lines, it
// generates the Qucs network. Notice that the schematic is split into three
// parts: components, wires and paintings, all of them are passed by reference.
void MatchDialog::SchematicParser(QString laddercode, int &x_pos, double Freq,
tSubstrate Substrate, bool microsyn) {
QStringList strlist = laddercode.split(";");//Slipt the string code to get the components
QString component, tag, label;
qDebug() << laddercode;
double value, value2, er, width;
int x_series = 120, x_shunt = 20; // x-axis spacing depending on whether the
// component is placed in a series or shunt
// configuration
// Clear schematic strings
QString componentstr = "", wirestr = "", paintingstr = "";
// The string format is as follows: "XX<value>;XX<value2>;...XX<valueN>;"
// where XX, YY, ZZ define the type of component and its configuration.
// LS: Series inductance
// CS: Series capacitor
// LP: Shunt inductance
// CP: Shunt capacitor
// TL: Series transmission line
// OU: Open stub (facing up)
// OL: Open stub (facing down)
// SU: Short circuited stub (facing up)
// SL: Short circuited stub (facing down)
// P1: Port 1
// LBL: LBL
// P2: Port 2
// DEV: Device label
// S2P: S-param simulation block
for (int i = 0; i < strlist.count(); i++) {
// Each token of the string descriptor has the following format:
// 'tag:<value>;''tag:<value1>#<value2>;'
// First, extract the tag
component = strlist.at(i);
int index_colon = component.indexOf(":");
tag = component.mid(0, index_colon);
// Now we remove the tag and the colon from the string
component.remove(0, index_colon + 1);
// At this point, we have the component parameters. In case of having more
// than one parameter, they are separated by #
int index = component.indexOf("#");
// Transmission lines are characterised by its impedance and its length
// whereas capacitors and inductors only depend on
// its capacitance and inductance, respectively. So, the code below is aimed
// to handle such difference
if (index != -1) // The component has two values
{
value = component.mid(0, index).toDouble();
value2 = component.mid(index + 1).toDouble();
} else {
if (!tag.compare("LBL")) // The value is a string
{
label = component;
} else // The value is a double number. For a clearer representation in
// the schematic, it will be shown with a limited precision
{
value = component.toDouble();
}
}
// The following if-else structure is responsible for placing the
// components, wires and paintings in the schematic
if (!tag.compare("P1")) // Port 1 component
{
componentstr += QStringLiteral("<Pac P1 1 %2 -30 18 -26 0 1 \"1\" 1 \"%1\" 1 "
"\"0 dBm\" 0 \"1 GHz\" 0>\n")
.arg(misc::num2str(value, 3, "Ohm")) // reference impedance
.arg(x_pos);
componentstr += QStringLiteral("<GND * 1 %1 0 0 0 0 0>\n").arg(x_pos);
wirestr += QStringLiteral("<%1 -60 %1 -120>\n").arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120>\n").arg(x_pos).arg(x_pos + 120);
x_pos += 120;
} else if (!tag.compare("LBL")) // Label
{
paintingstr += QStringLiteral("<Text %1 -150 12 #000000 0 \"%2\">\n")
.arg(x_pos)
.arg(component); // Add 'Port 1' or 'Port 2' label
x_pos += 50;
} else if (!tag.compare("P2")) // Port 2 component (it need to be different
// from P1 because of the wiring)
{
x_pos += 100;
componentstr += QStringLiteral("<Pac P2 1 %2 -30 18 -26 0 1 \"1\" 1 \"%1\" 1 "
"\"0 dBm\" 0 \"1 GHz\" 0>\n")
.arg(misc::num2str(value, 3, "Ohm")) // reference impedance
.arg(x_pos);
componentstr += QStringLiteral("<GND * 1 %1 0 0 0 0 0>\n").arg(x_pos);
wirestr += QStringLiteral("<%1 -60 %1 -120>\n").arg(x_pos); // Vertical wire
wirestr += QStringLiteral("<%1 -120 %2 -120>\n")
.arg(x_pos - 100)
.arg(x_pos); // Horizontal wire
} else if (!tag.compare("DEV")) // Device painting
{
paintingstr += QStringLiteral("<Text %1 -150 12 #000000 0 \"Device\">\n")
.arg(x_pos + 70); // Add 'Device' label
paintingstr +=
QStringLiteral("<Rectangle %1 -160 90 50 #000000 0 1 #c0c0c0 1 0>\n")
.arg(x_pos + 50); // Box surrounding the 'Device' label
x_pos += 200;
} else if (!tag.compare("LS")) // Series inductor
{
QString val = misc::num2str(value, 3, "H"); // Add prefix, unit - 3 significant digits
componentstr += QStringLiteral("<L L1 1 %1 -120 -26 10 0 0 \"%2\" 1 "
" 0>\n")
.arg(x_pos + 60)
.arg(val);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos)
.arg(x_pos + 30);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos + 90)
.arg(x_pos + x_series);
x_pos += x_series;
} else if (!tag.compare("CS")) // Series capacitor
{
QString val = misc::num2str(value, 3, "F"); // Add prefix, unit - 3 significant digits
componentstr += QStringLiteral("<C C1 1 %1 -120 -26 17 0 0 \"%2\" 1 "
" 0>\n")
.arg(x_pos + 60)
.arg(val);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos)
.arg(x_pos + 30);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos + 90)
.arg(x_pos + x_series);
x_pos += x_series;
} else if (!tag.compare("LP")) // Shunt inductor
{
QString val = misc::num2str(value, 3, "H"); // Add prefix, unit - 3 significant digits
componentstr += QStringLiteral("<GND * 1 %1 0 0 0 0 0>\n").arg(x_pos);
componentstr += QStringLiteral("<L L1 1 %1 -30 5 -20 0 1 \"%2\" 1 "
" 0>\n")
.arg(x_pos)
.arg(val);
wirestr += QStringLiteral("<%1 -60 %1 -120 "
" 0 0 0 "
">\n")
.arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos - 20)
.arg(x_pos + x_shunt);
x_pos += x_shunt;
} else if (!tag.compare("CP")) // Shunt capacitor
{
QString val = misc::num2str(value, 3, "F"); // Add prefix, unit - 3 significant digits
componentstr += QStringLiteral("<GND * 1 %1 0 0 0 0 0>\n").arg(x_pos);
componentstr += QStringLiteral("<C C1 1 %1 -30 15 -20 0 1 \"%2\" 1 "
" 0>\n")
.arg(x_pos)
.arg(val);
wirestr += QStringLiteral("<%1 -60 %1 -120 "
" 0 0 0 "
">\n")
.arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos - 20)
.arg(x_pos + x_shunt);
x_pos += x_shunt;
} else if (!tag.compare("TL")) // Transmission line
{
if (microsyn) // Microstrip implementation
{
er = Substrate.er;
getMicrostrip(value, Freq, &Substrate, width, er);
QString val_width =
misc::num2str(width, 3, "m"); // Add prefix, unit - 3 significant digits
QString val_length =
misc::num2str(value2 / sqrt(er), 3, "m"); // Add prefix, unit - 3 significant digits
componentstr +=
QStringLiteral("<MLIN MS1 1 %3 -120 -26 20 0 0 \"Sub1\" 1 \"%1\" 1 \"%2\" "
"1 \"Hammerstad\" 0 \"Kirschning\" 0 \"26.85\" 0>\n")
.arg(val_width)
.arg(val_length)
.arg(x_pos + 60);
} else {
// Add a prefix (although rarely needed here) and unit - 3 significant digits
QString val_impedance = misc::num2str(value, 3, "Ohm");
// Add prefix, unit - 3 significant digits
QString val_length = misc::num2str(value2, 3, "m");
componentstr += QStringLiteral("<TLIN Line1 1 %3 -120 -26 20 0 0 \"%1\" 1 "
"\"%2\" 1 \"0 dB\" 0 \"26.85\" 0>\n")
.arg(val_impedance)
.arg(val_length)
.arg(x_pos + 60);
}
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos)
.arg(x_pos + 30);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos + 90)
.arg(x_pos + x_series);
x_pos += x_series;
} else if (!tag.compare("OU")) // Open stub (upper)
{
if (microsyn) // Microstrip implementation
{
er = Substrate.er;
getMicrostrip(value, Freq, &Substrate, width, er);
// Add prefix, unit - 3 significant digits
QString val_width = misc::num2str(width, 3, "m");
// Add prefix, unit - 3 significant digits
QString val_length = misc::num2str(value2 / sqrt(er), 3, "m");
componentstr +=
QStringLiteral("<MLIN MS1 1 %3 -180 30 -30 0 1 \"Sub1\" 1 \"%1\" 1 \"%2\" "
"1 \"Hammerstad\" 0 \"Kirschning\" 0 \"26.85\" 0>\n")
.arg(val_width)
.arg(val_length)
.arg(x_pos);
} else {
// Add a prefix (although rarely needed here) and unit - 3 significant digits
QString val_impedance = misc::num2str(value, 3, "Ohm");
// Add prefix, unit - 3 significant digits
QString val_length = misc::num2str(value2, 3, "m");
componentstr += QStringLiteral("<TLIN Line1 1 %3 -180 30 -30 0 1 \"%1\" 1 "
"\"%2\" 1 \"0 dB\" 0 \"26.85\" 0>\n")
.arg(val_impedance)
.arg(val_length)
.arg(x_pos);
}
wirestr += QStringLiteral("<%1 -150 %1 -120 "
" 0 0 0 "
">\n")
.arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos)
.arg(x_pos + x_shunt);
// Here x_pos is not incremented since upper stubs does not overlap any
// other component
} else if (!tag.compare("OL")) // Open stub (lower)
{
if (microsyn) // Microstrip implementation
{
er = Substrate.er;
getMicrostrip(value, Freq, &Substrate, width, er);
// Add suffix mm, cm - 3 significant digits
QString val_width = misc::num2str(width, 3, "m");
// Add suffix mm, cm - 3 significant digits
QString val_length = misc::num2str(value2 / sqrt(er), 3, "m");
componentstr +=
QStringLiteral("<MLIN MS1 1 %3 -60 -26 30 0 1 \"Sub1\" 1 \"%1\" 1 \"%2\" "
"1 \"Hammerstad\" 0 \"Kirschning\" 0 \"26.85\" 0>\n")
.arg(val_width)
.arg(val_length)
.arg(x_pos);
} else {
// Add a prefix (although rarely needed here) and unit - 3 significant digits
QString val_impedance = misc::num2str(value, 3, "Ohm");
// Add suffix mm, cm - 3 significant digits
QString val_length = misc::num2str(value2, 3, "m");
componentstr += QStringLiteral("<TLIN Line1 1 %3 -60 -26 30 0 1 \"%1\" 1 "
"\"%2\" 1 \"0 dB\" 0 \"26.85\" 0>\n")
.arg(val_impedance)
.arg(val_length)
.arg(x_pos);
}
wirestr += QStringLiteral("<%1 -90 %1 -120 "
" 0 0 0 "
">\n")
.arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos - 20)
.arg(x_pos + x_shunt);
x_pos += x_shunt;
} else if (!tag.compare("SU")) // Short circuited stub (upper)
{
if (microsyn) // Microstrip implementation
{
er = Substrate.er;
getMicrostrip(value, Freq, &Substrate, width, er);
QString val_width = misc::num2str(width, 3, "m");
QString val_length = misc::num2str(value2 / sqrt(er), 3, "m");
componentstr +=
QStringLiteral("<MLIN MS1 1 %3 -180 30 -30 0 1 \"Sub1\" 1 \"%1\" 1 \"%2\" "
"1 \"Hammerstad\" 0 \"Kirschning\" 0 \"26.85\" 0>\n")
.arg(val_width)
.arg(val_length)
.arg(x_pos);
} else {
QString val_impedance = misc::num2str(value, 3, "Ohm");
QString val_length = misc::num2str(value2, 3, "m");
componentstr += QStringLiteral("<TLIN Line1 1 %3 -180 30 -30 0 1 \"%1\" 1 "
"\"%2\" 1 \"0 dB\" 0 \"26.85\" 0>\n")
.arg(val_impedance)
.arg(val_length)
.arg(x_pos);
}
componentstr += QStringLiteral("<GND * 1 %1 -210 0 0 0 2>\n").arg(x_pos);
wirestr += QStringLiteral("<%1 -150 %1 -120 "
" 0 0 0 "
">\n")
.arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos)
.arg(x_pos + x_shunt);
// Here x_pos is not incremented since upper stubs does not overlap any
// other component
} else if (!tag.compare("SL")) // Short circuited stub (lower)
{
if (microsyn) // Microstrip implementation
{
er = Substrate.er;
getMicrostrip(value, Freq, &Substrate, width, er);
QString val_width = misc::num2str(width, 3, "m");
QString val_length = misc::num2str(value2 / sqrt(er), 3, "m");
componentstr +=
QStringLiteral("<MLIN MS1 1 %3 -60 30 -30 0 1 \"Sub1\" 1 \"%1\" 1 \"%2\" "
"1 \"Hammerstad\" 0 \"Kirschning\" 0 \"26.85\" 0>\n")
.arg(val_width)
.arg(val_length)
.arg(x_pos);
} else {
QString val_impedance = misc::num2str(value, 3, "Ohm");
QString val_length = misc::num2str(value2, 3, "m");
componentstr += QStringLiteral("<TLIN Line1 1 %3 -60 20 30 0 1 \"%1\" 1 "
"\"%2\" 1 \"0 dB\" 0 \"26.85\" 0>\n")
.arg(val_impedance)
.arg(val_length)
.arg(x_pos);
}
componentstr += QStringLiteral("<GND * 1 %1 -30 0 0 0 0>\n").arg(x_pos);
wirestr += QStringLiteral("<%1 -90 %1 -120 "
" 0 0 0 "
">\n")
.arg(x_pos);
wirestr += QStringLiteral("<%1 -120 %2 -120 "
" 0 0 0 "
">\n")
.arg(x_pos - 20)
.arg(x_pos + x_shunt);
x_pos += x_shunt;
} else if (!tag.compare("S2P")) // S-param simulation block
{
// Add the frequency range for the S-param simulation
// cover 1 octave below and 1 above, user will adjust if needed...
double freq_start = Freq / 2.0;
double freq_stop = 2.0 *Freq;
QString val_freq_start = misc::num2str(freq_start, 3, "Hz");
QString val_freq_stop = misc::num2str(freq_stop, 3, "Hz");
componentstr +=
QStringLiteral("<.SP SP1 1 0 100 0 67 0 0 \"lin\" 1 \"%1\" 1 \"%2\" 1 "
"\"300\" 1 \"no\" 0 \"1\" 0 \"2\" 0>\n")
.arg((val_freq_start))
.arg((val_freq_stop));
if (laddercode.indexOf("P2") == -1) // One port simulation
componentstr += QStringLiteral("<Eqn Eqn1 1 200 100 -28 15 0 0 "
"\"S11_dB=dB(S[1,1])\" 1 \"yes\" 0>\n");
else // Two ports simulation
componentstr += QStringLiteral("<Eqn Eqn1 1 200 100 -28 15 0 0 "
"\"S11_dB=dB(S[1,1])\" 1 \"S21_dB=dB(S[2,1])\" "
"1 \"S22_dB=dB(S[2,2])\" 1 \"yes\" 0>\n");
} else if (!tag.compare("ZL")) // Complex load
{
double RL = value;
double XL = value2;
x_pos += 100;
if ((RL > 1e-3) && (XL < -1e-3)) // R + C
{
QString val_Res = misc::num2str(RL, 3, "Ohm");
// Need to use abs() because XL < 0
QString val_Cap = misc::num2str(1 / (fabs(XL) * 2 * pi * Freq), 3, "F");
componentstr +=
QString(
"<R R1 1 %1 -30 15 -26 0 -1 \"%2\" 1 \"26.85\" 0 \"US\" 0>\n")
.arg(x_pos)
.arg(val_Res);
componentstr += QStringLiteral("<C C1 1 %1 -90 15 -26 0 -1 \"%2\" 1 0>\n")
.arg(x_pos)
.arg(val_Cap);
paintingstr +=
QStringLiteral("<Text %1 50 12 #000000 0 \"%4-j%5 %2 @ %3\">\n")
.arg(x_pos)
.arg(QChar(0x2126))
.arg(misc::num2str(Freq, 3, "Hz"))
.arg(RL)
.arg(fabs(XL));
} else if ((RL > 1e-3) && (XL > 1e-3)) // R + L
{
QString val_Res = misc::num2str(RL, 3, "Ohm");
QString val_Ind = misc::num2str(XL / (2 * pi * Freq), 3, "H");
componentstr +=
QString(
"<R R1 1 %1 -30 15 -26 0 -1 \"%2\" 1 \"26.85\" 0 \"US\" 0>\n")
.arg(x_pos)
.arg(val_Res);
componentstr += QStringLiteral("<L L1 1 %1 -90 15 -26 0 -1 \"%2\" 1 0>\n")
.arg(x_pos)
.arg(val_Ind);
paintingstr +=
QStringLiteral("<Text %1 50 12 #000000 0 \"%4+j%5 %2 @ %3\">\n")
.arg(x_pos)
.arg(QChar(0x2126))
.arg(misc::num2str(Freq, 3, "Hz"))
.arg(RL)
.arg(XL);
} else if ((RL > 1e-3) && (fabs(XL) < 1e-3)) // R
{
QString val_Res = misc::num2str(RL, 3, "Ohm");
componentstr +=
QString(
"<R R1 1 %1 -30 15 -26 0 -1 \"%2\" 1 \"26.85\" 0 \"US\" 0>\n")
.arg(x_pos)
.arg(val_Res);
wirestr += QStringLiteral("<%1 -60 %1 -120>\n").arg(x_pos); // Vertical wire
paintingstr += QStringLiteral("<Text %1 50 12 #000000 0 \"%4 %2 @ %3\">\n")
.arg(x_pos)
.arg(QChar(0x2126))
.arg(misc::num2str(Freq, 3, "Hz"))
.arg(RL);
} else if ((RL < 1e-3) && (XL > 1e-3)) // L
{
QString val_Ind = misc::num2str(XL / (2 * pi * Freq), 3, "H");
componentstr +=
QString(
"<L L1 1 %1 -30 15 -26 0 -1 \"%2\" 1 \"26.85\" 0 \"US\" 0>\n")
.arg(x_pos)
.arg(val_Ind);
wirestr += QStringLiteral("<%1 -60 %1 -120>\n").arg(x_pos); // Vertical wire
paintingstr +=
QStringLiteral("<Text %1 50 12 #000000 0 \"j%4 %2 @ %3\">\n")
.arg(x_pos)
.arg(QChar(0x2126))
.arg(misc::num2str(Freq, 3, "Hz"))
.arg(XL);
} else if ((RL < 1e-3) && (XL < -1e-3)) // C
{
// Need to use abs() because XL < 0
QString val_Cap = misc::num2str(1 / (fabs(XL) * 2 * pi * Freq), 3, "F");
componentstr +=
QString(
"<C C1 1 %1 -30 15 -26 0 -1 \"%2\" 1 \"26.85\" 0 \"US\" 0>\n")
.arg(x_pos)
.arg(val_Cap);
wirestr += QStringLiteral("<%1 -60 %1 -120>\n").arg(x_pos); // Vertical wire
paintingstr +=
QStringLiteral("<Text %1 50 12 #000000 0 \"-j%4 %2 @ %3\">\n")
.arg(x_pos)
.arg(QChar(0x2126))
.arg(misc::num2str(Freq, 3, "Hz"))
.arg(fabs(XL));
}
wirestr += QStringLiteral("<%1 -120 %2 -120>\n")
.arg(x_pos - 100)
.arg(x_pos); // Horizontal wire
componentstr += QStringLiteral("<GND * 1 %1 0 0 -1 0 0>\n").arg(x_pos);
// Box surrounding the load
paintingstr +=
QStringLiteral("<Rectangle %1 -150 200 200 #000000 0 1 #c0c0c0 1 0>\n")
.arg(x_pos - 30);
}
}
// Substrate
if (microsyn)
componentstr +=
QStringLiteral("<SUBST Sub1 1 400 200 -30 24 0 0 \"%1\" 1 \"%2mm\" 1 \"%3um\" "
"1 \"%4\" 1 \"%5\" 1 \"%6\" 1>\n")
.arg(Substrate.er)
.arg(Substrate.height * 1e3)
.arg(Substrate.thickness * 1e6)
.arg(Substrate.tand)
.arg(Substrate.resistivity)
.arg(Substrate.roughness);
// Schematic header
QString Schematic = "<Qucs Schematic " PACKAGE_VERSION ">\n";
// Add components
Schematic += "<Components>\n";
Schematic += componentstr;
Schematic += "</Components>\n";
// Add wires
Schematic += "<Wires>\n";
Schematic += wirestr;
Schematic += "</Wires>\n";
// Add paintings
Schematic += "<Paintings>\n";
Schematic += paintingstr;
Schematic += "</Paintings>\n";
//Copy the schematic into clipboard
QApplication::clipboard()->setText(Schematic, QClipboard::Clipboard);
}