2.5 MiB
2.5 MiB
In [32]:
import numpy as np import skrf import skrf.media as media from skrf.media import DistributedCircuit import skrf.frequency as freq import skrf.network as net import skrf.util import matplotlib.pyplot as plt %matplotlib inline plt.rcParams['figure.figsize'] = [10, 10] f = freq.Frequency(0.1, 0.95, 1001) tem = DistributedCircuit(f, z0=50) bjt = net.Network('bfu520_7ma_attenuated10db_calibratedout_082519.s2p').interpolate(f) bjt bjt.plot_s_smith()
In [34]:
# well, on the bright side it stays mostly inside the unit circle # let's calculate the matching values for 144 MHz and 915 MHz # calculate the stability circles for the source and load impedances idx_915mhz = skrf.util.find_nearest_index(bjt.f, 915.e+6) idx_144mhz = skrf.util.find_nearest_index(bjt.f, 144.e+6) sqabs = lambda x: np.square(np.absolute(x)) delta = bjt.s11.s*bjt.s22.s - bjt.s12.s*bjt.s21.s rl = np.absolute((bjt.s12.s * bjt.s21.s)/(sqabs(bjt.s22.s) - sqabs(delta))) cl = np.conj(bjt.s22.s - delta*np.conj(bjt.s11.s))/(sqabs(bjt.s22.s) - sqabs(delta)) rl_915mhz = rl[idx_915mhz][0, 0] cl_915mhz = cl[idx_915mhz][0, 0] rl_144mhz = rl[idx_144mhz][0, 0] cl_144mhz = cl[idx_144mhz][0, 0] rl_144mhz, cl_144mhz, rl_915mhz, cl_915mhz
Out[34]:
(0.24538732275188085, (0.9099636993110758+0.5418029954577884j), 1.2327504787552828, (1.3139029137857448+1.8217439279569125j))
In [35]:
def calc_circle(c, r): theta = np.linspace(0, 2*np.pi, 1000) return c + r*np.exp(1.0j*theta) def plot_smith(pts, name=None): n = net.Network(s=pts, name=name) n.plot_s_smith() rs = np.absolute((bjt.s12.s * bjt.s21.s)/(sqabs(bjt.s11.s) - sqabs(delta))) cs = np.conj(bjt.s11.s - delta*np.conj(bjt.s22.s))/(sqabs(bjt.s11.s) - sqabs(delta)) rs_144mhz = rs[idx_144mhz][0, 0] cs_144mhz = cs[idx_144mhz][0, 0] rs_915mhz = rs[idx_915mhz][0, 0] cs_915mhz = cs[idx_915mhz][0, 0] print(rs_144mhz, cs_144mhz, rs_915mhz, cs_915mhz) cl_points = calc_circle(cl_915mhz, rl_915mhz) cs_points = calc_circle(cs_915mhz, rs_915mhz) plot_smith(cl_points) plot_smith(cs_points)
0.9628500183606965 (0.6456138840404008+1.3232778444002673j) 15.151970228344709 (13.724619885448135+3.3431448999354894j)
In [36]:
cl_points = calc_circle(cl_144mhz, rl_144mhz) cs_points = calc_circle(cs_144mhz, rs_144mhz) plot_smith(cl_points) plot_smith(cs_points)
In [37]:
# so the amplifier's pretty stable at 915 MHz, and we need to avoid inductive loads at 144 MHz # eh let's try a simultaneous conjugate match at 144 MHz after adding some shunt resistance, and see how stable that is # it looks like a little series output resistance would stabilize 915 MHz, so let's add some and then do a simultaneous # conjugate match there as well def conjugate_match(ntwk, idx): # that doesn't look too bad, so let's move forward and try to conjugate match delta2 = ntwk.s11.s[idx, 0, 0]*ntwk.s22.s[idx, 0, 0] - ntwk.s12.s[idx, 0, 0]*ntwk.s21.s[idx, 0, 0] B1 = 1 + sqabs(ntwk.s11.s[idx, 0, 0]) - sqabs(ntwk.s22.s[idx, 0, 0]) - sqabs(delta2) B2 = 1 + sqabs(ntwk.s22.s[idx, 0, 0]) - sqabs(ntwk.s11.s[idx, 0, 0]) - sqabs(delta2) C1 = ntwk.s11.s[idx, 0, 0] - delta2*np.conj(ntwk.s22.s[idx, 0, 0]) C2 = ntwk.s22.s[idx, 0, 0] - delta2*np.conj(ntwk.s11.s[idx, 0, 0]) gamma_s = (B1 - np.sqrt(np.square(B1) - 4*sqabs(C1) + 0j))/(2*C1) gamma_l = (B2 - np.sqrt(np.square(B2) - 4*sqabs(C2) + 0j))/(2*C2) z_s = net.s2z(np.array([[[gamma_s]]]))[0,0,0] z_l = net.s2z(np.array([[[gamma_l]]]))[0,0,0] return z_s, z_l bjt_144 = bjt ** tem.shunt(tem.resistor(110.) ** tem.short()) zs_144mhz, zl_144mhz = conjugate_match(bjt_144, idx_144mhz) zs_144mhz, zl_144mhz
Out[37]:
((84.01641752546088+116.58199865471924j), (79.98842398858322+49.29275687104739j))
In [38]:
def K(ntwk): delta2 = ntwk.s11.s*ntwk.s22.s - ntwk.s12.s*ntwk.s21.s k = ((1 - sqabs(ntwk.s11.s) - sqabs(ntwk.s22.s) + sqabs(delta2))/(2*np.absolute(ntwk.s12.s*ntwk.s21.s)))[:,0,0] return k plt.plot(bjt_144.f, K(bjt_144))
Out[38]:
[<matplotlib.lines.Line2D at 0x7f086821fe10>]
In [39]:
def calc_matching_network(z1, z2): flipped = ((abs(np.imag(z2)) < 1e-6 and np.real(z1) < np.real(z2)) or (abs(np.imag(z2)) > 1e-6 and np.real(z1) < np.real(1/(1/z2-1/(1.j*np.imag(z2)))))) if flipped: z2, z1 = z1, z2 # cancel out the imaginary parts of both input and output impedances z1_par = 1e+10 if abs(np.imag(z1)) > 1e-6: # parallel something to cancel out the imaginary part of # z1's impedance z1_par = 1/(-1j*np.imag(1/z1)) z1 = 1/(1./z1 + 1/z1_par) z2_ser = 0.0 if abs(np.imag(z2)) > 1e-6: z2_ser = -1j*np.imag(z2) z2 = z2 + z2_ser Q = np.sqrt((np.real(z1) - np.real(z2))/np.real(z2)) x1 = -1.j * np.real(z1)/Q x2 = 1.j * np.real(z2)*Q x1_tot = 1/(1/z1_par + 1/x1) x2_tot = z2_ser + x2 if flipped: return x2_tot, x1_tot else: return x1_tot, x2_tot zs_144mhz_1, zs_144mhz_2 = calc_matching_network(50., np.conj(zs_144mhz)) zl_144mhz_1, zl_144mhz_2 = calc_matching_network(np.conj(zl_144mhz), 50.) zs_144mhz_1, zs_144mhz_2, zl_144mhz_1, zl_144mhz_2
Out[39]:
(98.94107678366402j, -415.7338067603979j, -228.72461381828086j, 54.93861027793749j)
In [40]:
ls_144mhz = np.real(zs_144mhz_1/(2.j*np.pi*144e+6)) cs_144mhz = np.real(1/(2.j*np.pi*144e+6*zs_144mhz_2)) cl_144mhz = np.real(1/(2.j*np.pi*144e+6*zl_144mhz_1)) ll_144mhz = np.real(zl_144mhz_2/(2.j*np.pi*144e+6)) ls_144mhz, cs_144mhz, cl_144mhz, ll_144mhz
Out[40]:
(1.0935389892329788e-07, 2.6585344814100594e-12, 4.8321981701474735e-12, 6.072049578008999e-08)
In [41]:
# a little bit of playing around lets me figure out which one's the shunt and which is in series #input_network_144 = tem.inductor(ls_144mhz) ** tem.shunt_capacitor(cs_144mhz) input_network_144 = tem.inductor(120.e-9) ** tem.shunt_capacitor(3e-12) output_network_144 = tem.shunt_capacitor(4.7e-12) ** tem.inductor(ll_144mhz) amplifier_144 = input_network_144 ** bjt ** tem.shunt(tem.resistor(120.) ** tem.short()) ** output_network_144 amplifier_144.plot_s_smith()
In [42]:
amplifier_144.s21.plot_s_db()
In [43]:
amplifier_144.s11.plot_s_db()
In [44]:
amplifier_144.s22.plot_s_db()
In [45]:
# that looks pretty good, I'm going to set it up and see how well it actually performs # quick stability check (K>1 for stability) plt.plot(amplifier_144.f, K(amplifier_144))
Out[45]:
[<matplotlib.lines.Line2D at 0x7f0862f6ca20>]
In [46]:
# let's see how it actually measures bjt_144_matched_measurement = net.Network('bfu520_7ma_matched.s2p').interpolate(f) bjt_144_matched_measurement.plot_s_smith()
In [47]:
bjt_144_matched_measurement.s11.plot_s_db() bjt_144_matched_measurement.s22.plot_s_db()
In [48]:
bjt_144_matched_measurement.s21.plot_s_db()
In [49]:
# let's be accurate and use measurements from the new board f2 = freq.Frequency(0.1, 1.2, 1101) tem2 = DistributedCircuit(f2, z0=50) bjt_915 = net.Network('bfu520_7ma_083019_nomatch_10dbattenuated.s2p').interpolate(f2) bjt_915.plot_s_smith()
In [50]:
plt.plot(bjt_915.f, K(bjt_915))
Out[50]:
[<matplotlib.lines.Line2D at 0x7f0868052828>]
In [51]:
# stability's not great, let's figure out how to make it a little better def calc_load_circles(ntwk): delta = ntwk.s11.s*ntwk.s22.s - ntwk.s12.s*ntwk.s21.s rl = np.absolute((ntwk.s12.s * ntwk.s21.s)/(sqabs(ntwk.s22.s) - sqabs(delta))) cl = np.conj(ntwk.s22.s - delta*np.conj(ntwk.s11.s))/(sqabs(ntwk.s22.s) - sqabs(delta)) return cl, rl def calc_source_circles(ntwk): delta = ntwk.s11.s*ntwk.s22.s - ntwk.s12.s*ntwk.s21.s rs = np.absolute((ntwk.s12.s * ntwk.s21.s)/(sqabs(ntwk.s11.s) - sqabs(delta))) cs = np.conj(ntwk.s11.s - delta*np.conj(ntwk.s22.s))/(sqabs(ntwk.s11.s) - sqabs(delta)) return cs, rs load_circles_915 = calc_load_circles(bjt_915) source_circles_915 = calc_source_circles(bjt_915) for i in range(len(load_circles_915[0])): (cl, rl) = (load_circles_915[0][i], load_circles_915[1][i]) if i % 100 == 0: points = calc_circle(cl, rl) plot_smith(points, name=str(bjt_915.f[i]))
In [52]:
for i in range(len(source_circles_915[0])): (cl, rl) = (source_circles_915[0][i], source_circles_915[1][i]) if i % 100 == 0: points = calc_circle(cl, rl) plot_smith(points, name=str(bjt_915.f[i]))
In [53]:
# yeah I dunno, let's just conjugate match and see if it's stable, and mess with this stuff if it isn't idx_915mhz = skrf.util.find_nearest_index(bjt_915.f, 915.e+6) # it wasn't stable so I added a 200 ohm shunt resistor zs_915mhz, zl_915mhz = conjugate_match(bjt_915 ** tem2.shunt(tem2.resistor(200.) ** tem2.short()), idx_915mhz) zs_915mhz, zl_915mhz
Out[53]:
((18.936926346631363+0.5190209442606563j), (55.673493495726305+52.08945298641145j))
In [54]:
# as a quick aside let's see how much the resistor improved stability load_circles_915 = calc_load_circles(bjt_915 ** tem2.shunt(tem2.resistor(200.) ** tem2.short())) source_circles_915 = calc_source_circles(bjt_915 ** tem2.shunt(tem2.resistor(200.) ** tem2.short())) for i in range(len(load_circles_915[0])): (cl, rl) = (load_circles_915[0][i], load_circles_915[1][i]) if i % 100 == 0: points = calc_circle(cl, rl) plot_smith(points, name=str(bjt_915.f[i]))
In [55]:
for i in range(len(source_circles_915[0])): (cl, rl) = (source_circles_915[0][i], source_circles_915[1][i]) if i % 100 == 0: points = calc_circle(cl, rl) plot_smith(points, name=str(bjt_915.f[i]))
In [56]:
# so it helps a lot! good to know # let's calculate matching networks and whatever now zs_915mhz_1, zs_915mhz_2 = calc_matching_network(50., np.conj(zs_915mhz)) zl_915mhz_1, zl_915mhz_2 = calc_matching_network(np.conj(zl_915mhz), 50.) zs_915mhz_1, zs_915mhz_2, zl_915mhz_1, zl_915mhz_2
Out[56]:
((1.5240705538308625e-07-39.03934622699082j), 24.772662688828152j, -970.8839687874747j, 52.158236359336954j)
In [57]:
# aside from the ~-1000j (which I'm guessing is shunt), those all look like pretty reasonable values # let's see what they are ls_915mhz = np.real(zs_915mhz_2/(2.j*np.pi*915e+6)) cs_915mhz = np.real(1/(2.j*np.pi*915e+6*zs_915mhz_1)) cl_915mhz = np.real(1/(2.j*np.pi*915e+6*zl_915mhz_1)) ll_915mhz = np.real(zl_915mhz_2/(2.j*np.pi*915e+6)) ls_915mhz, cs_915mhz, cl_915mhz, ll_915mhz
Out[57]:
(4.308952699972857e-09, 4.455500548200138e-12, 1.7915614440823858e-13, 9.072394688026119e-09)
In [58]:
# a little bit of playing around lets me figure out which one's the shunt and which is in series #input_network_915 = tem.inductor(ls_915mhz) ** tem.shunt_capacitor(cs_915mhz) input_network_915 = tem2.shunt_capacitor(cs_915mhz) ** tem2.inductor(ls_915mhz) output_network_915 = tem2.inductor(ll_915mhz) amplifier_915 = input_network_915 ** bjt_915 ** tem2.shunt(tem2.resistor(200.) ** tem2.short()) ** output_network_915 amplifier_915.plot_s_smith()
In [59]:
# that doesn't quite look stable, but it might also just be because of measurement error; # there were some weird spikes in the scattering parameters anyways amplifier_915.s11.plot_s_db()
In [60]:
amplifier_915.s22.plot_s_db()
In [61]:
amplifier_915.s21.plot_s_db()
In [64]:
# let's see what the actual measurements look like amplifier_915_measured = net.Network('bfu520_7ma_matched915.s2p').interpolate(f2) amplifier_915_measured.plot_s_smith()
In [65]:
amplifier_915_measured.s11.plot_s_db()
In [66]:
amplifier_915_measured.s22.plot_s_db()
In [67]:
amplifier_915_measured.s21.plot_s_db()
In [ ]:
# not the best matching, but it's not too bad either # the gain is a little low at just 14 dB, but it's probably good enough # maybe next time I'll play around with some emitter inductor degeneration and actually using the noise data