Построение графика импеданса микрополосковой линии vs ширины с использованием UliEngineering

Следующие графики предоставляют простой способ визуализации зависимости между шириной микрополосковой линии и импедансом для различных высот и толщин подложки.

См. Толщина препрега и сердечника для различных стандартных стеков для получения дополнительной информации о стандартных толщинах стеков PCB и значениях $\epsilon_r$.

Microstrip Impedance.svg

Другой способ посмотреть на те же данные — построить график толщины диэлектрика vs ширины дорожки для предустановленных значений импеданса:

Microstrip Width mm.svg

или в mil:

Microstrip Width Mil.svg

Базовый пример

Этот скрипт на Python показывает, как вычислить импеданс микрополосковой линии для одного набора параметров с использованием библиотеки UliEngineering.

example_microstrip_width_calculation.py
#!/usr/bin/env python3
from UliEngineering.Electronics.Microstrip import microstrip_width
from UliEngineering.EngineerIO import format_value
from UliEngineering.EngineerIO.Length import convert_length_to_unit

# Вычисление ширины для 50 Ом микрополосковой линии с использованием строковых аргументов с единицами
w = microstrip_width("50 Ω", h="150 um", t="35 um", e_r="4.4")
# Преобразование результата (метры) в mils и вывод
w_mil = convert_length_to_unit(w, "m", "mil")
print(format_value(w_mil, "mil"))

Вывод примера скрипта

output.txt
10.2 mil

Исходный код графика

Этот скрипт используется для создания графика импеданса vs ширины, показанного выше.

plot_microstrip_impedance_vs_width.py
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter, AutoMinorLocator

# Импорт UliEngineering
from UliEngineering.Electronics.Microstrip import microstrip_impedance
from UliEngineering.EngineerIO import format_value
from UliEngineering.EngineerIO.Length import normalize_length

# Константы
e_r = 4.4
thicknesses = ["100um", "150um", "200um", "1550um"]
# Толщина меди (по умолчанию 35um, так как не указана, но требуется для вычисления)
t_copper = "35um"

# Ось ширины: от 1mil до 10mm, линейная
w_min = normalize_length("1mil")
w_max = normalize_length("10mm")
# Для логарифмической оси X нужны ширины, распределённые логарифмически; используем np.logspace
widths = np.logspace(np.log10(w_min), np.log10(w_max), 1000)

# Построение графика
plt.style.use("ggplot")
fig, ax = plt.subplots(figsize=(10, 12))

# Генерация цветов
colors = plt.cm.viridis(np.linspace(0, 1, len(thicknesses)))

for h_str, c in zip(thicknesses, colors):
    # Вычисление импеданса для каждой ширины
    # microstrip_impedance использует математические функции, поэтому не поддерживает массивы numpy напрямую
    zs = [microstrip_impedance(w, h=h_str, t=t_copper, e_r=e_r) for w in widths]
    
    ax.plot(widths, zs, color=c, lw=2, label=f"h={h_str}")

# Добавление легенды
legend = ax.legend(loc='upper right', fontsize='small')

ax.set_xscale("log")
ax.set_xlabel("Ширина (логарифмический масштаб)")
ax.set_ylabel("Импеданс")
ax.set_title(rf"Импеданс микрополосковой линии vs ширины ($\epsilon_r={e_r}$)")
ax.grid(True, which="both", ls="--")

# Форматирование осей с использованием EngineerIO format_value
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: format_value(x, "m")))
ax.yaxis.set_major_formatter(FuncFormatter(lambda y, pos: format_value(y, "Ω")))

# Включение промежуточных делений и добавление меток промежуточных делений на оси Y
ax.minorticks_on()
# Для линейного масштаба AutoMinorLocator обычно подходит, или можно позволить matplotlib обработать это.
# Шаблон использовал LogLocator, но у нас линейный масштаб.
ax.yaxis.set_minor_locator(AutoMinorLocator())
ax.yaxis.set_minor_formatter(FuncFormatter(lambda y, pos: format_value(y, "Ω")))

# Стиль меток промежуточных делений: на 30% меньше и 70% серый
maj_ylabels = ax.yaxis.get_ticklabels(which='major')
if len(maj_ylabels) > 0:
    base_size = maj_ylabels[0].get_size()
else:
    base_size = plt.rcParams.get('ytick.labelsize', plt.rcParams.get('font.size', 10))
ax.tick_params(axis='y', which='minor', labelsize=base_size * 0.7, labelcolor='0.7', colors='0.7')

# Также установка промежуточных делений оси X и их стилизация с меньшими повёрнутыми метками
from matplotlib.ticker import LogLocator
ax.xaxis.set_minor_locator(LogLocator(base=10.0, subs=(2, 3, 4, 5, 6, 7, 8, 9)))
ax.xaxis.set_minor_formatter(FuncFormatter(lambda x, pos: format_value(x, "m")))

maj_xlabels = ax.xaxis.get_ticklabels(which='major')
if len(maj_xlabels) > 0:
    base_x_size = maj_xlabels[0].get_size()
else:
    base_x_size = plt.rcParams.get('xtick.labelsize', plt.rcParams.get('font.size', 10))
ax.tick_params(axis='x', which='minor', labelsize=base_x_size * 0.7, labelcolor='0.7', colors='0.7')

for tl in ax.get_xminorticklabels():
    tl.set_rotation(90)
for tl in ax.get_xmajorticklabels():
    tl.set_rotation(90)

fig.tight_layout()
plt.show()
plt.savefig("Microstrip-Impedance.svg")

С другой стороны, следующий скрипт создаёт график толщины диэлектрика vs ширины для предустановленных значений импеданса (второй и третий графики, показанные выше, причём третий генерируется с использованием --y-unit mil).

plot_microstrip_width_vs_thickness.py
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
import argparse
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter, LogLocator

# Импорт UliEngineering
from UliEngineering.Electronics.Microstrip import microstrip_width
from UliEngineering.EngineerIO import format_value
from UliEngineering.EngineerIO.Length import normalize_length, convert_length_to_unit

# Командная строка: выбор единиц оси Y (метры по умолчанию или "mil")
parser = argparse.ArgumentParser(description="Построение графика ширины микрополосковой линии vs толщины диэлектрика")
parser.add_argument("--y-unit", choices=("m", "mil"), default="m",
                    help="Единицы для оси Y: 'm' для метров (по умолчанию) или 'mil' для mils")
args = parser.parse_args()
y_unit = args.y_unit

# Константы
e_r = 4.4
impedances = [50, 75, 100]
# Толщина меди
t_copper = "35um"

# Ось толщины: от 70um до 2.0mm (логарифмическая выборка для красивого распределения на логарифмической оси X)
h_min = normalize_length("70um")
h_max = normalize_length("2mm")
thicknesses = np.logspace(np.log10(h_min), np.log10(h_max), num=1000)

# Построение графика
plt.style.use("ggplot")
fig, ax = plt.subplots(figsize=(10, 8))

# Генерация цветов с использованием viridis для более приятных, перцептивно-однородных цветов
colors = plt.cm.viridis(np.linspace(0, 1, len(impedances)))

# Вычисление кривых ширины и их сбор для определения пределов оси Y при необходимости
all_ws = []
for z0, c in zip(impedances, colors):
    ws = [microstrip_width(Z0=z0, h=h, t=t_copper, e_r=e_r) for h in thicknesses]
    ax.plot(thicknesses, ws, color=c, lw=2, label=f"Z0={z0} Ω")
    all_ws.append(np.array(ws))

# Добавление легенды
legend = ax.legend(loc='upper left', fontsize='medium')

ax.set_xlabel("Толщина диэлектрика")
ax.set_ylabel(f"Ширина дорожки ({'м' if y_unit == 'm' else 'mil'})")
ax.set_title(rf"Ширина микрополосковой линии vs толщины диэлектрика ($\epsilon_r={e_r}$, $t_{{Cu}}={t_copper}$)")
ax.grid(True, which="both", ls="--")

# Логарифмический масштаб на обеих осях
ax.set_xscale("log")
ax.set_yscale("log")

# Установка пределов X от 70 мкм до 2.0 мм точно
ax.set_xlim(h_min, h_max)

# Настройка основных и промежуточных локаторов для логарифмических масштабов
major_x_locator = LogLocator(base=10.0)
minor_x_locator = LogLocator(base=10.0, subs=(2, 3, 4, 5, 6, 7, 8, 9))
major_y_locator = LogLocator(base=10.0)
minor_y_locator = LogLocator(base=10.0, subs=(2, 3, 4, 5, 6, 7, 8, 9))
ax.xaxis.set_major_locator(major_x_locator)
ax.xaxis.set_minor_locator(minor_x_locator)
ax.yaxis.set_major_locator(major_y_locator)
ax.yaxis.set_minor_locator(minor_y_locator)

# Форматирование осей с использованием EngineerIO format_value
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: format_value(x, "m")))

# Форматирование оси Y зависит от запрошенных единиц.
if y_unit == "m":
    ax.yaxis.set_major_formatter(FuncFormatter(lambda y, pos: format_value(y, "m")))
    ax.yaxis.set_minor_formatter(FuncFormatter(lambda y, pos: format_value(y, "m")))
else:
    # Форматирование значений в mils без SI-префиксов с использованием вспомогательной функции
    def format_mil_no_prefix(y, pos):
        mils = convert_length_to_unit(y, "m", "mil")
        if mils >= 100:
            s = f"{int(round(mils))} mil"
        elif mils >= 10:
            s = f"{mils:.1f} mil"
        elif mils >= 1:
            s = f"{mils:.2f} mil"
        else:
            s = f"{mils:.3f} mil"
        return s

    ax.yaxis.set_major_formatter(FuncFormatter(format_mil_no_prefix))
    ax.yaxis.set_minor_formatter(FuncFormatter(format_mil_no_prefix))

# Включение промежуточных делений
ax.minorticks_on()
ax.xaxis.set_minor_formatter(FuncFormatter(lambda x, pos: format_value(x, "m")))

# Вычисление и установка делений X, ограниченных запрошенным диапазоном X
major_locs = major_x_locator.tick_values(h_min, h_max)
minor_locs = minor_x_locator.tick_values(h_min, h_max)
major_locs = np.array([m for m in major_locs if m >= h_min and m <= h_max])
minor_locs = np.array([m for m in minor_locs if m >= h_min and m <= h_max])

# Добавление специального промежуточного деления при X = 1.55 мм
special_tick = normalize_length("1.55mm")
if special_tick not in minor_locs:
    minor_locs = np.sort(np.concatenate((minor_locs, [special_tick])))

# Явная установка делений
ax.set_xticks(major_locs, minor=False)
ax.set_xticks(minor_locs, minor=True)

# Также рисование тонкой вертикальной направляющей на 1.55 мм для выделения специальной точки
ax.axvline(special_tick, color='0.5', linestyle=':', linewidth=1)

# Вычисление диапазона Y из данных и установка соответствующих делений в режиме mil
if len(all_ws) > 0:
    y_all = np.concatenate(all_ws)
    y_all = y_all[y_all > 0]
    if len(y_all) > 0:
        y_min = np.min(y_all)
        y_max = np.max(y_all)
        # отступы
        y_pad = (np.log10(y_max) - np.log10(y_min)) * 0.05 if y_max > y_min else 0.1
        ax.set_ylim(10 ** (np.log10(y_min) - y_pad), 10 ** (np.log10(y_max) + y_pad))

        if y_unit == 'mil':
            yl, yu = ax.get_ylim()
            yl_mil = convert_length_to_unit(yl, 'm', 'mil')
            yu_mil = convert_length_to_unit(yu, 'm', 'mil')
            # выбор основных декад в mils (10, 100, 1000, ...)
            dmin = int(np.floor(np.log10(max(yl_mil, 1e-12))))
            dmax = int(np.ceil(np.log10(max(yu_mil, 1e-12))))
            # Обеспечение включения 1 mil (10^0) как основного деления, когда возможно
            dstart = max(0, dmin)
            majors_mil = [10 ** d for d in range(dstart, dmax + 1)]
            majors_m = [convert_length_to_unit(m, 'mil', 'm') for m in majors_mil]
            minors_m = []
            for d in range(dstart, dmax + 1):
                for s in (2, 3, 4, 5, 6, 7, 8, 9):
                    v_mil = s * (10 ** d)
                    minors_m.append(convert_length_to_unit(v_mil, 'mil', 'm'))
            majors_m = np.array([m for m in majors_m if m >= yl and m <= yu])
            minors_m = np.array([m for m in minors_m if m >= yl and m <= yu])
            ax.set_yticks(majors_m, minor=False)
            ax.set_yticks(minors_m, minor=True)

# Поворот меток, если нужно
for tl in ax.get_xminorticklabels():
    tl.set_rotation(90)
for tl in ax.get_xmajorticklabels():
    tl.set_rotation(90)

# Стиль меток промежуточных делений: на 30% меньше и 70% серый (обе оси)
maj_ylabels = ax.yaxis.get_ticklabels(which='major')
if len(maj_ylabels) > 0:
    base_y_size = maj_ylabels[0].get_size()
else:
    base_y_size = plt.rcParams.get('ytick.labelsize', plt.rcParams.get('font.size', 10))
maj_xlabels = ax.xaxis.get_ticklabels(which='major')
if len(maj_xlabels) > 0:
    base_x_size = maj_xlabels[0].get_size()
else:
    base_x_size = plt.rcParams.get('xtick.labelsize', plt.rcParams.get('font.size', 10))
ax.tick_params(axis='y', which='minor', labelsize=base_y_size * 0.7, labelcolor='0.7', colors='0.7')
ax.tick_params(axis='x', which='minor', labelsize=base_x_size * 0.7, labelcolor='0.7', colors='0.7')

fig.tight_layout()
plt.savefig("Microstrip-Width.svg", dpi=300, bbox_inches='tight')
# Интерактивный показ только при работе не в headless CI
try:
    plt.show()
except Exception:
    pass

Check out similar posts by category: Electronics, RF