main
Colby Knox Altium Designer build of the TPS54202 buck molecule: Altium project, manufacturing set, STEP and GLB, design, simulatio… 845dd0d 4d ago
#!/usr/bin/env python3
"""TPS54202 12 V -> 5 V / 1 A buck: every value from the datasheet equations (SLVSD26C).

Run: python3 calcs.py  -> prints the design table and writes calcs.json.
"""
import itertools, json, math

# ---- brief --------------------------------------------------------------
VIN_NOM, VIN_MAX, VIN_MIN = 12.0, 16.0, 9.0   # 12 V bench/control-panel rail; design margin to 16 V
VOUT, IOUT = 5.0, 1.0
FSW = 500e3                                    # fixed, datasheet 5.5 (390..630 kHz)
VREF = 0.596                                   # datasheet 5.5, 0.581..0.611
RHS, RLS = 0.148, 0.078                        # FET on-resistance, 25 C
ILIM_HS_MIN = 2.5

out = {}

# ---- feedback divider from Adom-stocked 0402 values (E24 subset, 41 values) ----
STOCK = [1, 1.5, 2.2, 3.3, 4.7, 6.8, 10, 15, 22, 33, 47, 68, 100, 120, 150, 220, 330, 470, 680,
         1e3, 1.5e3, 2e3, 2.2e3, 3.3e3, 4.7e3, 5.6e3, 6.8e3, 10e3, 15e3, 22e3, 33e3, 47e3, 68e3,
         100e3, 150e3, 220e3, 330e3, 470e3, 680e3, 1e6]
cands = []
single = [r for r in STOCK if 5e3 <= r <= 1e6]
pairs = [(a, b) for a, b in itertools.combinations_with_replacement(single, 2)]
for rtop_parts in [(r,) for r in single] + pairs:
    rtop = sum(rtop_parts)
    for rbot_parts in [(r,) for r in single] + pairs:
        rbot = sum(rbot_parts)
        if not (5e3 <= rbot <= 30e3) or not (50e3 <= rtop <= 250e3):
            continue                           # keep divider current 20..100 uA, Rtop in the Cff range
        v = VREF * (1 + rtop / rbot)
        n = len(rtop_parts) + len(rbot_parts)
        cands.append((abs(v - VOUT), n, rtop_parts, rbot_parts, v))
cands.sort(key=lambda c: (c[1] > 3, round(c[0], 3), c[1]))
err, n, rt, rb, v = cands[0]
out["feedback"] = {"Rtop_parts_ohm": rt, "Rbot_parts_ohm": rb, "Rtop": sum(rt), "Rbot": sum(rb),
                   "vout_nominal": round(v, 4), "error_pct": round(100 * (v - VOUT) / VOUT, 2),
                   "vout_range_vref_tol": [round(0.581 * (1 + sum(rt) / sum(rb)), 3),
                                           round(0.611 * (1 + sum(rt) / sum(rb)), 3)]}

# ---- inductor (eq 8/9/10) ----------------------------------------------
KIND = 0.3
LMIN = VOUT * (VIN_MAX - VOUT) / (VIN_MAX * KIND * IOUT * FSW)
L = 15e-6
ripple = lambda vin: VOUT * (vin - VOUT) / (vin * L * FSW)
out["inductor"] = {"Lmin_uH": round(LMIN * 1e6, 2), "L_uH": 15,
                   "ripple_A_at_12V": round(ripple(VIN_NOM), 3), "ripple_A_at_16V": round(ripple(VIN_MAX), 3),
                   "Ipeak_A_at_16V": round(IOUT + ripple(VIN_MAX) / 2, 3),
                   "Irms_A": round(math.sqrt(IOUT**2 + ripple(VIN_MAX)**2 / 12), 3),
                   "Isat_required_A": ILIM_HS_MIN,
                   "note": ("Isat 3.7 A / Irms 3.5 A (AMPLH5030S-150M datasheet p2) >= the 2.5 A HS current-limit minimum, DCR 98 typ / 118 max mOhm. "
                           "15 uH chosen over Lmin 22.9 uH (KIND 0.3 at 16 V): it is the 5 V value in TI Table 7-2, which the internal slope compensation is tuned for; "
                           "ripple 0.39 A (39 pct) at 12 V and 0.46 A (46 pct) at 16 V, peak 1.23 A, within 20-50 pct for low-ESR ceramic outputs")}

# ---- output capacitance (eq 11/12/13/14) -------------------------------
dI_step, dV_allow = 1.0, 0.15                  # 0 -> 1 A step, 3 % droop budget
CO_step = 2 * dI_step / (FSW * dV_allow)
CO_ripple = 1 / (8 * FSW * (0.010 / ripple(VIN_MAX)))   # 10 mV ripple target
N_COUT, C_EACH, DERATE = 3, 22e-6, 0.55        # CL21A226MPQNNNE 0805 X5R 10 V; ~45 % loss at 5 V DC bias
CO_eff = N_COUT * C_EACH * DERATE
fo = 3.95e3 / (VOUT * CO_eff * 1e6) * 1e3      # eq 14, fo in Hz (Cout in uF, result kHz)
out["cout"] = {"C_step_min_uF": round(CO_step * 1e6, 1), "C_ripple_min_uF": round(CO_ripple * 1e6, 1),
               "chosen": f"{N_COUT} x 22 uF 0805 X5R 10 V (Adom stock)", "effective_uF": round(CO_eff * 1e6, 1),
               "fo_kHz_eq14": round(fo / 1e3, 1), "fo_limit_kHz": 40,
               "ripple_mV_at_16V": round(ripple(VIN_MAX) / (8 * FSW * CO_eff) * 1e3, 2),
               "Icout_rms_each_mA": round(ripple(VIN_MAX) / math.sqrt(12) / N_COUT * 1e3, 1)}

# ---- feed-forward capacitor (eq 16) ------------------------------------
CFF = 1 / (2 * math.pi * fo * out["feedback"]["Rtop"])
out["cff"] = {"calc_pF": round(CFF * 1e12, 1), "chosen_pF": 68,
              "note": "C0G 0402 stock; verified by the loop-gain sim"}

# ---- input capacitance --------------------------------------------------
D = VOUT / VIN_NOM
ICIN_RMS = IOUT * math.sqrt(D * (1 - D))
CIN_eff = 2 * 10e-6 * 0.6
dVin = IOUT * D * (1 - D) / (CIN_eff * FSW)
out["cin"] = {"Irms_A": round(ICIN_RMS, 3), "chosen": "2 x 10 uF 1206 X7R 25 V (Mouser) + 100 nF 0603 X7R 50 V (Adom stock) at the VIN pin",
              "effective_uF": CIN_eff * 1e6, "vin_ripple_mV": round(dVin * 1e3, 1),
              "bulk": "47 uF 35 V aluminium electrolytic: damps the supply cable inductance against the low-ESR ceramics (hot-plug ringing), datasheet 7.3"}

# ---- EN UVLO divider (stocked values) ----------------------------------
IP, IH, VEN_R, VEN_F = 0.7e-6, 1.55e-6, 1.21, 1.19
R_EN_TOP, R_EN_BOT = 1e6, 150e3
vstart = R_EN_TOP * (VEN_R / R_EN_BOT - IP) + VEN_R
vstop = R_EN_TOP * (VEN_F / R_EN_BOT - IP - IH) + VEN_F
ven_max = VIN_MAX * R_EN_BOT / (R_EN_TOP + R_EN_BOT)
out["uvlo"] = {"R_top": R_EN_TOP, "R_bot": R_EN_BOT, "start_V": round(vstart, 2), "stop_V": round(vstop, 2),
               "hysteresis_V": round(vstart - vstop, 2), "EN_at_16V": round(ven_max, 2), "EN_abs_max": 5.5,
               "EN_at_28V": round(28 * R_EN_BOT / (R_EN_TOP + R_EN_BOT), 2)}

# ---- losses + thermal ---------------------------------------------------
DCR = 0.118                                    # Abracon AMPLH5030S-150MT (Mouser 815-AMPLH5030S-150T)
Irms = out["inductor"]["Irms_A"]
p_cond = Irms**2 * (D * RHS * 1.4 + (1 - D) * RLS * 1.4)       # hot Rds(on) x1.4
p_sw = 0.5 * VIN_NOM * IOUT * (15e-9 + 15e-9) * FSW             # ~15 ns edges, estimate
p_gate = 0.01
p_ic = p_cond + p_sw + p_gate
p_l = Irms**2 * DCR
eff = VOUT * IOUT / (VOUT * IOUT + p_ic + p_l)
out["thermal"] = {"P_ic_W": round(p_ic, 3), "P_inductor_W": round(p_l, 3), "efficiency_est": round(eff, 3),
                  "Tj_rise_C_RthJA_118.6": round(p_ic * 118.6, 1), "Tj_rise_C_RthJA_EVM_57.2": round(p_ic * 57.2, 1),
                  "note": "I^2R at hot Rds(on) over the duty + switching estimate; SOT-23 has no pad, heat leaves through the pins: GND/VIN copper is the heatsink"}

# ---- LED ----------------------------------------------------------------
out["led"] = {"R_ohm": 2.2e3, "I_mA": round((VOUT - 2.0) / 2.2e3 * 1e3, 2), "note": "power-good indicator on VOUT; 0603 green (Adom stock)"}

json.dump(out, open("calcs.json", "w"), indent=1)
for k, v in out.items():
    print(f"== {k}")
    for kk, vv in v.items():
        print(f"   {kk}: {vv}")