Representación gráfica de la impedancia de microtira en función del ancho usando UliEngineering
Los siguientes gráficos proporcionan una forma sencilla de visualizar la relación entre el ancho de microtira y la impedancia para diversas alturas y espesores de sustrato.
Consulte Prepreg and core thickness for different standard stackups para más información sobre los espesores de los stackups de PCB estándar y los valores de $\epsilon_r$.
Otra forma de analizar los mismos datos es representar el espesor dieléctrico frente al ancho de la pista para valores de impedancia preestablecidos:
o en mil:
Ejemplo básico
Este script de Python muestra cómo calcular la impedancia de microtira para un único conjunto de parámetros usando la librería UliEngineering.
#!/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
# Calcular el ancho para una microtira de 50 Ω usando argumentos de cadena con unidades
w = microstrip_width("50 Ω", h="150 um", t="35 um", e_r="4.4")
# Convertir el resultado (metros) a mils e imprimir
w_mil = convert_length_to_unit(w, "m", "mil")
print(format_value(w_mil, "mil"))Salida del script de ejemplo
10.2 milCódigo fuente del gráfico
Este script se utiliza para generar el gráfico de impedancia frente a ancho mostrado arriba.
#!/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
# Importaciones de UliEngineering
from UliEngineering.Electronics.Microstrip import microstrip_impedance
from UliEngineering.EngineerIO import format_value
from UliEngineering.EngineerIO.Length import normalize_length
# Constantes
e_r = 4.4
thicknesses = ["100um", "150um", "200um", "1550um"]
# Espesor de cobre (por defecto 35um ya que no se especificó pero es necesario para el cálculo)
t_copper = "35um"
# Eje de anchos: de 1mil a 10mm lineal
w_min = normalize_length("1mil")
w_max = normalize_length("10mm")
# Para un eje x logarítmico necesitamos anchos espaciados logarítmicamente; usar np.logspace
widths = np.logspace(np.log10(w_min), np.log10(w_max), 1000)
# Representación gráfica
plt.style.use("ggplot")
fig, ax = plt.subplots(figsize=(10, 12))
# Generar colores
colors = plt.cm.viridis(np.linspace(0, 1, len(thicknesses)))
for h_str, c in zip(thicknesses, colors):
# Calcular la impedancia para cada ancho
# microstrip_impedance usa funciones matemáticas por lo que no soporta arrays de numpy directamente
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}")
# Añadir una leyenda
legend = ax.legend(loc='upper right', fontsize='small')
ax.set_xscale("log")
ax.set_xlabel("Width (log scale)")
ax.set_ylabel("Impedance")
ax.set_title(rf"Microstrip Impedance vs Width ($\epsilon_r={e_r}$)")
ax.grid(True, which="both", ls="--")
# Formateadores de ejes usando 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, "Ω")))
# Habilitar marcas menores y añadir etiquetas de marcas menores en el eje Y
ax.minorticks_on()
# Para escala lineal, AutoMinorLocator suele ser adecuado, o podemos dejar que matplotlib lo maneje.
# La plantilla usaba LogLocator, pero nosotros somos lineales.
ax.yaxis.set_minor_locator(AutoMinorLocator())
ax.yaxis.set_minor_formatter(FuncFormatter(lambda y, pos: format_value(y, "Ω")))
# Estilizar etiquetas de marcas menores: 30% más pequeñas y 70% grises
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')
# También establecer marcas menores del eje x y estilizarlas con etiquetas más pequeñas y rotadas
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")Por otro lado, el siguiente script produce un gráfico del espesor dieléctrico frente a valores de impedancia preestablecidos (el segundo y tercer gráfico mostrados arriba, con el tercero generado usando --y-unit mil).
#!/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
# Importaciones de 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
# Línea de comandos: elegir unidades del eje Y (metros por defecto, o "mil")
parser = argparse.ArgumentParser(description="Plot microstrip width vs dielectric thickness")
parser.add_argument("--y-unit", choices=("m", "mil"), default="m",
help="Units for the Y axis: 'm' for meters (default) or 'mil' for mils")
args = parser.parse_args()
y_unit = args.y_unit
# Constantes
e_r = 4.4
impedances = [50, 75, 100]
# Espesor de cobre
t_copper = "35um"
# Eje de espesores: de 70um a 2.0mm (muestreo logarítmico para que los valores estén bien espaciados en un eje x logarítmico)
h_min = normalize_length("70um")
h_max = normalize_length("2mm")
thicknesses = np.logspace(np.log10(h_min), np.log10(h_max), num=1000)
# Representación gráfica
plt.style.use("ggplot")
fig, ax = plt.subplots(figsize=(10, 8))
# Generar colores usando viridis para colores más agradables y uniformes perceptualmente
colors = plt.cm.viridis(np.linspace(0, 1, len(impedances)))
# Calcular las trazas de ancho y recopilarlas para determinar los límites Y cuando sea necesario
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))
# Añadir una leyenda
legend = ax.legend(loc='upper left', fontsize='medium')
ax.set_xlabel("Dielectric Thickness")
ax.set_ylabel(f"Trace Width ({'m' if y_unit == 'm' else 'mil'})")
ax.set_title(rf"Microstrip Width vs Dielectric Thickness ($\epsilon_r={e_r}$, $t_{{Cu}}={t_copper}$)")
ax.grid(True, which="both", ls="--")
# Escala logarítmica en ambos ejes
ax.set_xscale("log")
ax.set_yscale("log")
# Aplicar límites X exactamente de 70 µm a 2.0 mm
ax.set_xlim(h_min, h_max)
# Configurar localizadores mayores y menores para escalas logarítmicas
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)
# Formateadores de ejes usando EngineerIO format_value
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: format_value(x, "m")))
# El formateador del eje Y depende de la unidad solicitada.
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:
# Formatear valores en mils sin prefijos SI usando el helper
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))
# Habilitar marcas menores
ax.minorticks_on()
ax.xaxis.set_minor_formatter(FuncFormatter(lambda x, pos: format_value(x, "m")))
# Calcular y establecer las marcas X restringidas al rango X solicitado
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])
# Añadir una marca menor especial en X = 1.55 mm
special_tick = normalize_length("1.55mm")
if special_tick not in minor_locs:
minor_locs = np.sort(np.concatenate((minor_locs, [special_tick])))
# Establecer las marcas explícitamente
ax.set_xticks(major_locs, minor=False)
ax.set_xticks(minor_locs, minor=True)
# También dibujar una guía vertical sutil en 1.55 mm para enfatizar el punto especial
ax.axvline(special_tick, color='0.5', linestyle=':', linewidth=1)
# Calcular el rango Y a partir de los datos y establecer marcas apropiadas si está en modo 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)
# relleno
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')
# elegir décadas mayores en 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))))
# Asegurar que incluimos 1 mil (10^0) como marca mayor cuando sea posible
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)
# Rotar etiquetas si es necesario
for tl in ax.get_xminorticklabels():
tl.set_rotation(90)
for tl in ax.get_xmajorticklabels():
tl.set_rotation(90)
# Estilizar etiquetas de marcas menores: 30% más pequeñas y 70% grises (ambos ejes)
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')
# Solo mostrar interactivamente cuando no se ejecute en CI sin interfaz gráfica
try:
plt.show()
except Exception:
pass