molecule
12 V to 5 V / 1 A Buck Molecule (TPS54202), built in Altium Designer
Public Unreviewedby Colby Knox
A 12 V to 5 V, 1 A TPS54202 buck molecule designed and built by AI in Altium Designer from one sentence, with the Altium project, Gerbers, BOM, simulation and current and heat analysis.
main
Colby Knox
Altium Designer build of the TPS54202 buck molecule: Altium project, manufacturing set, STEP and GLB, design, simulatio…
845dd0d
4d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
#!/usr/bin/env python3
"""Switching transient of the TPS54202 5 V / 1 A design in ngspice (XSPICE latch, cycle by cycle).
Peak-current-mode modulator: a 500 kHz clock sets an SR latch, the latch resets when
Ri*iL + slope ramp >= Vc (or iL hits the 2.5 A minimum high-side limit). Synchronous FETs are
switches with the datasheet Rds(on); the error amplifier is the same calibrated equivalent as
loop.py, with the 5 ms soft start as a ramp on its reference.
Test: VIN 12 V, 10 ohm load plus a switched 10 ohm step (1 A total) from t=0; the step opens 7 to 8 ms: 1 A -> 0.5 A -> 1 A.
Run: python3 transient.py -> transient.json, transient.png
"""
import json, math, os, subprocess
import numpy as np
HERE = os.path.dirname(os.path.abspath(__file__))
calcs = json.load(open(os.path.join(HERE, "..", "design", "calcs.json")))
loop = json.load(open(os.path.join(HERE, "loop.json")))["model"]
VIN = float(os.environ.get("VIN", 12))
VOUT, FSW, L = 5.0, 500e3, 15e-6
RTOP, RBOT = calcs["feedback"]["Rtop"], calcs["feedback"]["Rbot"]
CFF = calcs["cff"]["chosen_pF"] * 1e-12
COUT = calcs["cout"]["effective_uF"] * 1e-6
RI, GM, RC, CC, CP = loop["Ri"], loop["gm"], loop["Rc"], loop["Cc"], loop["Cp"]
SE = 0.5 * VOUT / L * RI # slope comp V/s
TSTOP = 9e-3
cir = f"""* TPS54202 switching transient
.option method=gear reltol=1e-3
Vsrc src 0 {VIN}
Rcable src vinc 0.05
Lcable vinc vin 0.5u
Cbulk vin b1 47u
Rbulk b1 0 0.3
Cin vin 0 12u
* power stage
Shs vin sw hg 0 swm_hs
Sls sw 0 lg 0 swm_ls
Dls 0 sw dbody
Lo sw lx {L}
Rdcr lx vout 0.118
Cout vout ce {COUT}
Resr ce 0 0.001
Rload vout 0 10
Rstep vout st 10
Sstep st 0 stp 0 swm_step
Vstp stp 0 PWL(0 1 7m 1 7.0001m 0 8m 0 8.0001m 1)
* feedback
Rtop vout fb {RTOP}
Cff vout fb {CFF}
Rbot fb 0 {RBOT}
* soft-start reference 0 -> 0.596 V over 5 ms
Bref vref 0 V = min(0.596, 0.596*time/5m)
Gea vc 0 fb vref {GM}
Rea vc 0 {1000/GM}
Rc vc cz {RC}
Cc cz 0 {CC}
Cp vc 0 {CP}
* modulator
Vclk clk 0 PULSE(0 1 0 1n 1n 20n {1/FSW})
Bramp ramp 0 V = {SE} * (time - floor(time*{FSW})/{FSW})
Brst rst 0 V = ((i(Vsense)*{RI} + v(ramp) >= v(vc)) || (i(Vsense) >= 2.5)) ? 1 : 0
Vsense lx lxs 0
.model swm_hs sw vt=0.5 vh=0 ron=0.148 roff=1meg
.model swm_ls sw vt=0.5 vh=0 ron=0.078 roff=1meg
.model swm_step sw vt=0.5 vh=0 ron=0.001 roff=1g
.model dbody d is=1e-9 n=1.2 rs=0.05
aset [clk rst] [dset drst] adc
.model adc adc_bridge in_low=0.4 in_high=0.6
apu den pullup
.model pullup d_pullup
alatch dset drst den NULL NULL dq dqb latch
.model latch d_srlatch sr_value=0 rise_delay=5n fall_delay=5n
adac [dq dqb] [q qb] dac
.model dac dac_bridge out_low=0 out_high=1 t_rise=2n t_fall=2n
* 10 ns dead time: gate drives through RC delay
Bhg hg 0 V = v(q) > 0.5 ? 1 : 0
Rqd qb qbd 1k
Cqd qbd 0 10p
Blg lg 0 V = (v(qbd) > 0.9) && (v(q) < 0.5) ? 1 : 0
.tran 10n {TSTOP} 0 10n uic
.control
set wr_singlescale
run
wrdata {HERE}/_tr.txt v(vout) i(Vsense) v(sw) v(vc)
.endc
.end
"""
# Vsense sits in series with the inductor: move the DCR after it
cir = cir.replace("Lo sw lx {L}".format(L=L), f"Lo sw lx {L}")
cir = cir.replace("Rdcr lx vout 0.118", "Rdcr lxs vout 0.118")
open(os.path.join(HERE, "_tr.cir"), "w").write(cir)
r = subprocess.run(["ngspice", "-b", os.path.join(HERE, "_tr.cir")], capture_output=True, text=True)
if r.returncode:
print(r.stdout[-3000:], r.stderr[-3000:])
raise SystemExit(1)
d = np.loadtxt(os.path.join(HERE, "_tr.txt"))
t, vo, il, vsw, vc = d[:, 0], d[:, 1], d[:, 2], d[:, 3], d[:, 4]
def win(a, b):
m = (t >= a) & (t <= b)
return m
steady = win(6.5e-3, 7.0e-3)
half = win(7.6e-3, 8.0e-3)
res = {
"vin": VIN,
"vout_mean_1A": round(float(vo[steady].mean()), 4),
"vout_ripple_pp_mV_1A": round(float(np.ptp(vo[steady])) * 1e3, 2),
"il_ripple_pp_A": round(float(np.ptp(il[win(6.9e-3, 7.0e-3)])), 3),
"il_peak_startup_A": round(float(il[t < 6e-3].max()), 3),
"vout_overshoot_startup_mV": round(float((vo[t < 6.5e-3].max() - vo[steady].mean()) * 1e3), 1),
"t_reach_95pct_ms": round(float(t[np.argmax(vo > 0.95 * vo[steady].mean())] * 1e3), 3),
"step_down_1A_to_0.5A_overshoot_mV": round(float((vo[win(7e-3, 7.5e-3)].max() - vo[steady].mean()) * 1e3), 1),
"step_up_0.5A_to_1A_undershoot_mV": round(float((vo[half].mean() - vo[win(8e-3, 8.5e-3)].min()) * 1e3), 1),
}
settle = win(8e-3, TSTOP)
ts = t[settle]; err = np.abs(vo[settle] - vo[steady].mean())
out_band = ts[err > 0.01]
res["step_up_settle_1pct_us"] = round(float((out_band.max() - 8e-3) * 1e6), 1) if len(out_band) else 0.0
json.dump(res, open(os.path.join(HERE, f"transient_{int(VIN)}V.json"), "w"), indent=1)
print(json.dumps(res, indent=1))
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, ax = plt.subplots(3, 1, figsize=(11, 8))
ax[0].plot(t * 1e3, vo); ax[0].set_ylabel("VOUT V"); ax[0].set_title(f"TPS54202 12 V→5 V switching transient, VIN {VIN:g} V (ngspice, cycle-by-cycle PCM)")
ax[0].grid(alpha=0.3)
m = win(6.8e-3, 9e-3)
ax[1].plot(t[m] * 1e3, vo[m] * 1e3 - 5000); ax[1].set_ylabel("VOUT − 5 V (mV)"); ax[1].grid(alpha=0.3)
ax[1].axvline(7, color="k", lw=0.5, ls="--"); ax[1].axvline(8, color="k", lw=0.5, ls="--")
ax[1].text(7.02, ax[1].get_ylim()[1] * 0.8, "1 A→0.5 A"); ax[1].text(8.02, ax[1].get_ylim()[1] * 0.8, "0.5 A→1 A")
m2 = win(6.98e-3, 7.0e-3)
ax[2].plot(t[m2] * 1e6, il[m2], label="iL (A)"); ax[2].plot(t[m2] * 1e6, vsw[m2] / VIN, label="SW / VIN", alpha=0.6)
ax[2].set_xlabel("µs"); ax[2].legend(); ax[2].grid(alpha=0.3)
fig.tight_layout(); fig.savefig(os.path.join(HERE, f"transient_{int(VIN)}V.png"), dpi=110)