From ee3467e5ec3ae1afe6be68b201fa36cbb31327fd Mon Sep 17 00:00:00 2001 From: Roel Jordans Date: Fri, 10 Feb 2023 18:40:23 +0100 Subject: [PATCH] Calculating mu of a core based on given dimensions --- NanoVNASaver/Charts/RIMu.py | 170 ++++++++++++++++++++++++++++++++ NanoVNASaver/Charts/__init__.py | 1 + NanoVNASaver/NanoVNASaver.py | 5 +- 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 NanoVNASaver/Charts/RIMu.py diff --git a/NanoVNASaver/Charts/RIMu.py b/NanoVNASaver/Charts/RIMu.py new file mode 100644 index 0000000..40952fd --- /dev/null +++ b/NanoVNASaver/Charts/RIMu.py @@ -0,0 +1,170 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020,2021 NanoVNA-Saver Authors +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import math +import numpy as np +import logging +from scipy.constants import mu_0 + +from PyQt5 import QtWidgets, QtGui + +from NanoVNASaver.Formatting import format_frequency_chart +from NanoVNASaver.RFTools import Datapoint +from NanoVNASaver.Charts.Chart import Chart + +from .RI import RealImaginaryChart + +logger = logging.getLogger(__name__) + +MU = "\N{GREEK SMALL LETTER MU}" + +class RealImaginaryMuChart(RealImaginaryChart): + + def __init__(self, name=""): + super().__init__(name) + self.y_menu.addSeparator() + + self.action_set_fixed_maximum_real = QtWidgets.QAction( + f"Maximum {MU}' ({self.maxDisplayReal})") + self.action_set_fixed_maximum_real.triggered.connect( + self.setMaximumRealValue) + + self.action_set_fixed_minimum_real = QtWidgets.QAction( + f"Minimum {MU}' ({self.minDisplayReal})") + self.action_set_fixed_minimum_real.triggered.connect( + self.setMinimumRealValue) + + self.action_set_fixed_maximum_imag = QtWidgets.QAction( + f"Maximum {MU}'' ({self.maxDisplayImag})") + self.action_set_fixed_maximum_imag.triggered.connect( + self.setMaximumImagValue) + + self.action_set_fixed_minimum_imag = QtWidgets.QAction( + f"Minimum {MU}'' ({self.minDisplayImag})") + self.action_set_fixed_minimum_imag.triggered.connect( + self.setMinimumImagValue) + + self.y_menu.addAction(self.action_set_fixed_maximum_real) + self.y_menu.addAction(self.action_set_fixed_minimum_real) + self.y_menu.addSeparator() + self.y_menu.addAction(self.action_set_fixed_maximum_imag) + self.y_menu.addAction(self.action_set_fixed_minimum_imag) + + # Manage core parameters + # TODO pick some sane default values? + self.coreLength = 1. + self.coreArea = 1. + self.coreWindings = 1 + + self.y_menu.addSeparator() + self.action_set_core_length = QtWidgets.QAction( + "Core effective length") + self.action_set_core_length.triggered.connect( + self.setCoreLength) + + self.y_menu.addSeparator() + self.action_set_core_area = QtWidgets.QAction( + "Core area") + self.action_set_core_area.triggered.connect( + self.setCoreArea) + + self.y_menu.addSeparator() + self.action_set_core_windings = QtWidgets.QAction( + "Core number of windings") + self.action_set_core_windings.triggered.connect( + self.setCoreWindings) + + self.y_menu.addAction(self.action_set_core_length) + self.y_menu.addAction(self.action_set_core_area) + self.y_menu.addAction(self.action_set_core_windings) + + def drawChart(self, qp: QtGui.QPainter): + qp.setPen(QtGui.QPen(Chart.color.text)) + qp.drawText(self.leftMargin + 5, 15, + f"{self.name}") + qp.drawText(5, 15, f"{MU}'") + qp.drawText(self.leftMargin + self.dim.width + 10, 15, f"{MU}''") + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.topMargin + self.dim.height + 5) + qp.drawLine(self.leftMargin-5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width + 5, + self.topMargin + self.dim.height) + self.drawTitle(qp) + + def contextMenuEvent(self, event): + self.action_set_fixed_start.setText( + f"Start ({format_frequency_chart(self.minFrequency)})") + self.action_set_fixed_stop.setText( + f"Stop ({format_frequency_chart(self.maxFrequency)})") + self.action_set_fixed_minimum_real.setText( + f"Minimum {MU}' ({self.minDisplayReal})") + self.action_set_fixed_maximum_real.setText( + f"Maximum {MU}' ({self.maxDisplayReal})") + self.action_set_fixed_minimum_imag.setText( + f"Minimum {MU}'' ({self.minDisplayImag})") + self.action_set_fixed_maximum_imag.setText( + f"Maximum {MU}'' ({self.maxDisplayImag})") + self.menu.exec_(event.globalPos()) + + def setCoreLength(self): + val, selected = QtWidgets.QInputDialog.getDouble( + self, "Core effective length", + "Set core effective length in mm", value=self.coreLength, + decimals=2) + if not selected: + return + if not (self.fixedValues and val >= 0): + self.coreLength = val + if self.fixedValues: + self.update() + + def setCoreArea(self): + val, selected = QtWidgets.QInputDialog.getDouble( + self, "Core effective area", + "Set core cross section area length in mm\N{SUPERSCRIPT TWO}", value=self.coreArea, + decimals=2) + if not selected: + return + if not (self.fixedValues and val >= 0): + self.coreArea = val + if self.fixedValues: + self.update() + + def setCoreWindings(self): + val, selected = QtWidgets.QInputDialog.getInt( + self, "Core number of windings", + "Set core number of windings", value=self.coreWindings) + if not selected: + return + if not (self.fixedValues and val >= 0): + self.coreWindings = val + if self.fixedValues: + self.update() + + def value(self, p: Datapoint) -> complex: + return self.mu_r(p) + + def mu_r(self, p: Datapoint) -> complex: + inductance = np.conj(-1j * p.impedance()/(2*math.pi*p.freq)) + + # Core length and core area are in mm and mm2 respectively + return inductance * (self.coreLength/1e3) / (mu_0 * self.coreWindings**2 * (self.coreArea/1e6)) diff --git a/NanoVNASaver/Charts/__init__.py b/NanoVNASaver/Charts/__init__.py index 30c5520..087d7ca 100644 --- a/NanoVNASaver/Charts/__init__.py +++ b/NanoVNASaver/Charts/__init__.py @@ -15,6 +15,7 @@ from .Permeability import PermeabilityChart from .Phase import PhaseChart from .QFactor import QualityFactorChart from .RI import RealImaginaryChart +from .RIMu import RealImaginaryMuChart from .RIZ import RealImaginaryZChart from .RIZShunt import RealImaginaryZShuntChart from .RIZSeries import RealImaginaryZSeriesChart diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 796e91f..576d68b 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -45,7 +45,9 @@ from .Charts import ( MagnitudeChart, MagnitudeZChart, MagnitudeZShuntChart, MagnitudeZSeriesChart, QualityFactorChart, VSWRChart, PermeabilityChart, PolarChart, - RealImaginaryChart, RealImaginaryZChart, RealImaginaryZShuntChart, RealImaginaryZSeriesChart, + RealImaginaryChart, + RealImaginaryMuChart, + RealImaginaryZChart, RealImaginaryZShuntChart, RealImaginaryZSeriesChart, SmithChart, SParameterChart, TDRChart, ) from .Calibration import Calibration @@ -152,6 +154,7 @@ class NanoVNASaver(QtWidgets.QWidget): "phase": PhaseChart("S11 Phase"), "q_factor": QualityFactorChart("S11 Quality Factor"), "real_imag": RealImaginaryZChart("S11 R+jX"), + "real_imag_mu": RealImaginaryMuChart("S11 \N{GREEK SMALL LETTER MU}"), "smith": SmithChart("S11 Smith Chart"), "s_parameter": SParameterChart("S11 Real/Imaginary"), "vswr": VSWRChart("S11 VSWR"),