|
import math |
|
|
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
|
|
from lcapy import Series, Shunt, L, C, R |
|
|
|
import PySpice.Logging.Logging as Logging |
|
|
|
logger = Logging.setup_logging() |
|
|
|
from PySpice.Plot.BodeDiagram import bode_diagram_gain |
|
from PySpice.Spice.Netlist import Circuit |
|
from PySpice.Spice.BasicElement import Capacitor, Inductor |
|
from PySpice.Unit import * |
|
|
|
_DEBUG_ = False |
|
|
|
|
|
def debug(msg): |
|
if _DEBUG_: |
|
print(msg) |
|
|
|
|
|
# fmt: off |
|
# R ratio = 1 |
|
cheb_1db_ripple_tab = { |
|
3: [2.023593, 0.994102, 2.023593], |
|
5: [2.134882, 1.091107, 3.000923, 1.091107, 2.134882], |
|
7: [2.166557, 1.111509, 3.093642, 1.173521, 3.093642, 1.111509, 2.166557], |
|
9: [2.179723, 1.119177, 3.121434, 1.189673, 3.174634, 1.189673, 3.121434, 1.119177, 2.179723], |
|
11: [2.186417, 1.122901, 3.133679, 1.195713, 3.197903, 1.204134, 3.197903, 1.195713, 3.133679, 1.122901, 2.186417], |
|
} |
|
|
|
cheb_01db_ripple_tab = { |
|
3: [1.03156, 1.147397, 1.03156], |
|
5: [1.146813, 1.371213, 1.975003, 1.371213, 1.146813], |
|
7: [1.181178, 1.422806, 2.096671, 1.573401, 2.096671, 1.422806, 1.181178], |
|
9: [1.195672, 1.442601, 2.134551, 1.616723, 2.205369, 1.616723, 2.134551, 1.442601, 1.195672], |
|
11: [1.203089, 1.452293, 2.151438, 1.633242, 2.237794, 1.655924, 2.237794, 1.633242, 2.151438, 1.452293, 1.203089], |
|
} |
|
# fmt: on |
|
|
|
# These are values available at the time |
|
standardized_capacitance_values = [2.2, 12, 15, 33, 47, 100] |
|
|
|
|
|
def norm_cap(c): |
|
c *= 1e12 # in pF |
|
cap_val = [ |
|
c / 2 for c in standardized_capacitance_values |
|
] # admit series connections |
|
cap_val += [ |
|
c + d |
|
for c in standardized_capacitance_values |
|
for d in standardized_capacitance_values |
|
] # admit parallel connections |
|
return 1e-12 * round(cap_val[np.argmin([abs(cs - c) for cs in cap_val])], 2) |
|
|
|
|
|
def get_circuit(lc_ser, lc_par, Rs=50, Rp=50): |
|
circuit = Circuit("Cheb bandstop") |
|
circuit.SinusoidalVoltageSource("input", "inp", circuit.gnd, amplitude=1 @ u_V) |
|
|
|
circuit.R("s", "inp", 1, Rs) |
|
circuit.R("p", "outp", circuit.gnd, Rp) |
|
|
|
for i, (l, c) in enumerate(lc_par): |
|
p1 = i + 1 |
|
p2 = "outp" if i + 1 == len(lc_par) else i + 2 |
|
|
|
circuit.L(f"{i+1}p", p1, p2, (l * 1e9) @ u_nH) |
|
circuit.C(f"{i+1}p", p1, p2, (c * 1e12) @ u_pF) |
|
|
|
for i, (l, c) in enumerate(lc_ser): |
|
p1 = "outp" if i + 1 == len(lc_ser) else i + 1 |
|
|
|
circuit.C(f"{i+1}s", p1, f"{i + 1}s", (c * 1e12) @ u_pF) |
|
circuit.L(f"{i+1}s", f"{i + 1}s", circuit.gnd, (l * 1e9) @ u_nH) |
|
|
|
return circuit |
|
|
|
|
|
def pretty_circuit(circuit): |
|
lines = [] |
|
for e in circuit.elements: |
|
if isinstance(e, Capacitor): |
|
v = round(e.capacitance.value, 2) |
|
u = e.capacitance.prefixed_unit |
|
lines.append(f"{e.name} = {v} {u}") |
|
elif isinstance(e, Inductor): |
|
v = round(e.inductance.value, 2) |
|
u = e.inductance.prefixed_unit |
|
lines.append(f"{e.name} = {v} {u}") |
|
|
|
return "\n".join(lines) |
|
|
|
|
|
def get_sym_network(n): |
|
if n % 2 == 0: |
|
raise ValueError("Filter order must be odd") |
|
|
|
net = Shunt(C("C1s") + L("L1s")) |
|
for i in range(n - 1): |
|
if i % 2 == 0: |
|
net = net.chain(Series(C(f"C{i // 2 + 1}p") | L(f"L{i // 2 + 1}p"))) |
|
else: |
|
net = net.chain(Shunt(C(f"C{i // 2 + 2}s") + L(f"L{i // 2 + 2}s"))) |
|
|
|
return net |
|
|
|
|
|
def cheb_bandstop_des(A_min, f_pl, f_ph, f_sl, f_sh, A_max=1, k_m=50): |
|
|
|
w_pl, w_ph, w_sl, w_sh = list( |
|
map(lambda f: 2 * math.pi * f, [f_pl, f_ph, f_sl, f_sh]) |
|
) |
|
|
|
debug("--------------------------------") |
|
debug(f"w_pl = {w_pl} rad/s") |
|
debug(f"w_ph = {w_ph} rad/s") |
|
|
|
debug(f"w_sl = {w_sl} rad/s") |
|
debug(f"w_sh = {w_sh} rad/s") |
|
|
|
w_bw = w_ph - w_pl |
|
w_center = math.sqrt(w_ph * w_pl) |
|
|
|
debug(f"w_bw = {w_bw} rad/s ({w_bw / (2*math.pi)} Hz)") |
|
debug(f"w_center = {w_center} rad/s ({w_center / (2*math.pi)} Hz)") |
|
|
|
omega_p = w_bw / (w_ph - w_pl) |
|
omega_s = w_bw / (w_sh - w_sl) |
|
|
|
debug(f"omega_p = {omega_p}") |
|
debug(f"omega_s = {omega_s}") |
|
|
|
epsilon_p = math.sqrt(math.pow(10, A_max / 10) - 1) |
|
epsilon_s = math.sqrt(math.pow(10, A_min / 10) - 1) |
|
|
|
debug(f"epsilon_p = {epsilon_p}") |
|
debug(f"epsilon_s = {epsilon_s}") |
|
|
|
n = math.ceil(math.acosh(epsilon_s / epsilon_p) / math.acosh(omega_s / omega_p)) |
|
if n % 2 == 0: |
|
n += 1 |
|
|
|
print(f"n = {n}") |
|
|
|
if not n in cheb_1db_ripple_tab.keys(): |
|
raise ValueError(f"Unsupported filter order: {n}") |
|
|
|
k_fbs = w_bw |
|
|
|
if A_max == 1: |
|
norm_values = cheb_1db_ripple_tab[n] |
|
elif A_max == 0.1: |
|
norm_values = cheb_01db_ripple_tab[n] |
|
else: |
|
raise ValueError("Unsupported A_max") |
|
|
|
ser_v = [norm_values[i] for i in range(0, len(norm_values), 2)] |
|
par_v = [norm_values[i] for i in range(1, len(norm_values), 2)] |
|
|
|
def ser_compute(v): |
|
C = (k_fbs * v) / (k_m * (w_center**2)) |
|
L = k_m / (k_fbs * v) |
|
return L, C |
|
|
|
def par_compute(v): |
|
C = 1 / (k_m * k_fbs * v) |
|
L = (k_m * k_fbs * v) / (w_center**2) |
|
return L, C |
|
|
|
LC_ser = list(map(ser_compute, ser_v)) |
|
debug(f"LC_serial = {LC_ser}") |
|
|
|
LC_par = list(map(par_compute, par_v)) |
|
debug(f"LC_parallel = {LC_par}") |
|
|
|
return n, LC_ser, LC_par |
|
|
|
|
|
f_pl, f_ph, f_sl, f_sh = [80e6, 116e6, 88e6, 108e6] # MHz |
|
n, lc_ser, lc_par = cheb_bandstop_des(45, f_pl, f_ph, f_sl, f_sh, 0.1) |
|
|
|
circuit_ideal = get_circuit(lc_ser, lc_par, Rp=75) |
|
pc = pretty_circuit(circuit_ideal) |
|
print("Ideal filter values") |
|
print(pc) |
|
|
|
lc_ser = [(1e-9 * round(l * 1e9), norm_cap(c)) for l, c in lc_ser] |
|
lc_par = [(1e-9 * round(l * 1e9), norm_cap(c)) for l, c in lc_par] |
|
|
|
circuit = get_circuit(lc_ser, lc_par, Rp=75) |
|
pc = pretty_circuit(circuit) |
|
print("Rounded filter values") |
|
print(pc) |
|
|
|
net = get_sym_network(n) |
|
net.cct.draw("filter_circuit.png", label_nodes=False, draw_nodes="connections") |
|
|
|
net = Series(R("Rs")).chain(net.chain(Shunt(R("Rp")))) |
|
s_circuit = net.cct |
|
|
|
s_circuit = s_circuit.subs("Rp", 75) |
|
s_circuit = s_circuit.subs("Rs", 50) |
|
|
|
for i, (l, c) in enumerate(lc_ser): |
|
s_circuit = s_circuit.subs(f"L{i + 1}s", l) |
|
s_circuit = s_circuit.subs(f"C{i + 1}s", c) |
|
|
|
for i, (l, c) in enumerate(lc_par): |
|
s_circuit = s_circuit.subs(f"L{i + 1}p", l) |
|
s_circuit = s_circuit.subs(f"C{i + 1}p", c) |
|
|
|
s_circuit.draw( |
|
"filter_circuit_with_values.png", |
|
label_nodes=False, |
|
draw_nodes="connections", |
|
node_spacing=4.0, |
|
) |
|
|
|
figure, ax = plt.subplots(figsize=(20, 10)) |
|
|
|
simulator = circuit_ideal.simulator(temperature=25, nominal_temperature=25) |
|
analysis = simulator.ac( |
|
start_frequency=f_pl * 0.5 @ u_Hz, |
|
stop_frequency=f_ph * 1.5 @ u_Hz, |
|
number_of_points=1000, |
|
variation="dec", |
|
) |
|
|
|
plt.title("Bode Diagram Gain") |
|
bode_diagram_gain( |
|
axe=ax, |
|
frequency=analysis.frequency, |
|
gain=20 * np.log10(np.absolute(analysis.outp)), |
|
marker=".", |
|
color="blue", |
|
linestyle="-", |
|
label="Ideal", |
|
) |
|
|
|
simulator = circuit.simulator(temperature=25, nominal_temperature=25) |
|
analysis = simulator.ac( |
|
start_frequency=f_pl * 0.5 @ u_Hz, |
|
stop_frequency=f_ph * 1.5 @ u_Hz, |
|
number_of_points=1000, |
|
variation="dec", |
|
) |
|
|
|
bode_diagram_gain( |
|
axe=ax, |
|
frequency=analysis.frequency, |
|
gain=20 * np.log10(np.absolute(analysis.outp)), |
|
marker=".", |
|
color="red", |
|
linestyle="-", |
|
label="Rounded values", |
|
) |
|
|
|
plt.text(0.05, 0.8, pc, fontsize="20", ha="left", va="top", transform=ax.transAxes) |
|
plt.tight_layout() |
|
plt.legend(fontsize="20", loc="lower right") |
|
|
|
# H = s_circuit.transfer(0, 1, 2, 3) |
|
# H.bode_plot((f_pl * 0.5, f_ph * 1.5), axes=ax, dbmin=None) |
|
|
|
plt.show() |