Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Measuring mu' and mu'' of ferrite cores #595

Merged
merged 6 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 6 additions & 49 deletions NanoVNASaver/Charts/RI.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,33 +72,7 @@ def __init__(self, name=""):
mode_group.addAction(self.y_action_fixed_span)
self.y_menu.addAction(self.y_action_automatic)
self.y_menu.addAction(self.y_action_fixed_span)
self.y_menu.addSeparator()

self.action_set_fixed_maximum_real = QtWidgets.QAction(
f"Maximum R ({self.maxDisplayReal})")
self.action_set_fixed_maximum_real.triggered.connect(
self.setMaximumRealValue)

self.action_set_fixed_minimum_real = QtWidgets.QAction(
f"Minimum R ({self.minDisplayReal})")
self.action_set_fixed_minimum_real.triggered.connect(
self.setMinimumRealValue)

self.action_set_fixed_maximum_imag = QtWidgets.QAction(
f"Maximum jX ({self.maxDisplayImag})")
self.action_set_fixed_maximum_imag.triggered.connect(
self.setMaximumImagValue)

self.action_set_fixed_minimum_imag = QtWidgets.QAction(
f"Minimum jX ({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)

def copy(self):
new_chart: RealImaginaryChart = super().copy()
Expand All @@ -109,23 +83,6 @@ def copy(self):
new_chart.minDisplayImag = self.minDisplayImag
return new_chart

def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(self.leftMargin + 5, 15,
f"{self.name} (\N{OHM SIGN})")
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X")
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 drawValues(self, qp: QtGui.QPainter):
if not self.data and not self.reference:
return
Expand Down Expand Up @@ -333,7 +290,7 @@ def find_scaling(self):
max_real = 0
max_imag = -1000
for d in self.data:
imp = self.impedance(d)
imp = self.value(d)
re, im = imp.real, imp.imag
if math.isinf(re): # Avoid infinite scales
continue
Expand All @@ -345,7 +302,7 @@ def find_scaling(self):
for d in self.reference:
if d.freq < self.fstart or d.freq > self.fstop:
continue
imp = self.impedance(d)
imp = self.value(d)
re, im = imp.real, imp.imag
if math.isinf(re): # Avoid infinite scales
continue
Expand Down Expand Up @@ -393,12 +350,12 @@ def imag_scaling_constraints(self, min_imag, max_imag):
return min_imag, max_imag

def getImYPosition(self, d: Datapoint) -> int:
im = self.impedance(d).imag
im = self.value(d).imag
return int(self.topMargin + (self.max_imag - im) / self.span_imag
* self.dim.height)

def getReYPosition(self, d: Datapoint) -> int:
re = self.impedance(d).real
re = self.value(d).real
return int(self.topMargin + (self.max_real - re) / self.span_real
* self.dim.height if math.isfinite(re) else self.topMargin)

Expand Down Expand Up @@ -521,5 +478,5 @@ def contextMenuEvent(self, event):
f"Maximum jX ({self.maxDisplayImag})")
self.menu.exec_(event.globalPos())

def impedance(self, p: Datapoint) -> complex:
return p.impedance()
def value(self, p: Datapoint) -> complex:
raise NotImplementedError()
178 changes: 178 additions & 0 deletions NanoVNASaver/Charts/RIMu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# 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 <https://www.gnu.org/licenses/>.
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.menu.addSeparator()
self.action_set_core_length = QtWidgets.QAction(
"Core effective length")
self.action_set_core_length.triggered.connect(
self.setCoreLength)

self.action_set_core_area = QtWidgets.QAction(
"Core area")
self.action_set_core_area.triggered.connect(
self.setCoreArea)

self.action_set_core_windings = QtWidgets.QAction(
"Core number of windings")
self.action_set_core_windings.triggered.connect(
self.setCoreWindings)

self.menu.addAction(self.action_set_core_length)
self.menu.addAction(self.action_set_core_area)
self.menu.addAction(self.action_set_core_windings)

def copy(self):
new_chart: RealImaginaryMuChart = super().copy()

new_chart.coreLength = self.coreLength
new_chart.coreArea = self.coreArea
new_chart.coreWindings = self.coreWindings

return new_chart

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 = p.impedance()/(2j*math.pi*p.freq)

# Core length and core area are in mm and mm2 respectively
# note: mu_r = mu' - j * mu ''
return np.conj(inductance * (self.coreLength/1e3) / (mu_0 * self.coreWindings**2 * (self.coreArea/1e6)))
98 changes: 98 additions & 0 deletions NanoVNASaver/Charts/RIZ.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# 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 <https://www.gnu.org/licenses/>.
import logging

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__)

class RealImaginaryZChart(RealImaginaryChart):
def __init__(self, name=""):
super().__init__(name)
self.y_menu.addSeparator()

self.action_set_fixed_maximum_real = QtWidgets.QAction(
f"Maximum R ({self.maxDisplayReal})")
self.action_set_fixed_maximum_real.triggered.connect(
self.setMaximumRealValue)

self.action_set_fixed_minimum_real = QtWidgets.QAction(
f"Minimum R ({self.minDisplayReal})")
self.action_set_fixed_minimum_real.triggered.connect(
self.setMinimumRealValue)

self.action_set_fixed_maximum_imag = QtWidgets.QAction(
f"Maximum jX ({self.maxDisplayImag})")
self.action_set_fixed_maximum_imag.triggered.connect(
self.setMaximumImagValue)

self.action_set_fixed_minimum_imag = QtWidgets.QAction(
f"Minimum jX ({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)

def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(self.leftMargin + 5, 15,
f"{self.name} (\N{OHM SIGN})")
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X")
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 R ({self.minDisplayReal})")
self.action_set_fixed_maximum_real.setText(
f"Maximum R ({self.maxDisplayReal})")
self.action_set_fixed_minimum_imag.setText(
f"Minimum jX ({self.minDisplayImag})")
self.action_set_fixed_maximum_imag.setText(
f"Maximum jX ({self.maxDisplayImag})")
self.menu.exec_(event.globalPos())

def value(self, p: Datapoint) -> complex:
return self.impedance(p)

def impedance(self, p: Datapoint) -> complex:
return p.impedance()
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
import logging

from NanoVNASaver.RFTools import Datapoint
from .RI import RealImaginaryChart
from .RIZ import RealImaginaryZChart

logger = logging.getLogger(__name__)


class RealImaginarySeriesChart(RealImaginaryChart):
class RealImaginaryZSeriesChart(RealImaginaryZChart):

def impedance(self, p: Datapoint) -> complex:
return p.seriesImpedance()
Loading