#!/usr/bin/env python3 """build_power.py — generate the CM5-carrier Power sheet, stage by stage. Same connectivity model as build_ekey.py: every component pin gets a named label (or a power:GND symbol) coincident with its connection point, so nets form by name with no wire routing. Coords are grid-snapped to 1.27mm so labels bind to pins. STAGE 1 (this file): input protection — J_PWR(15V) -> D_REV(LM74700 ideal-diode + Q_REV N-FET) -> VIN_PROT, with input TVS + bulk. Reverse-polarity + reverse-current via the ideal-diode controller. Later stages (bucks) append to PLACEMENTS and re-run. Datasheet items to CONFIRM against LM74700-Q1 (values, not topology): - C_VCAP value/reference (charge-pump reservoir; ~1uF typ) - EN handling (tied to +15V_IN for always-on; EN abs-max covers 15V) - Q_REV FET pick (Vds>=30V, low Rds(on), SOA for inrush) + optional gate resistor - TVS standoff (SMBJ18A: 18V standoff / ~29V clamp < buck Vin rating) """ import re, sys, os, uuid as uuidlib ROOT_UUID = "e63e39d7-6ac0-4ffd-8aa3-1841a4541b55" PROJECT = "CM5IO" KSS = "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols" HERE = os.path.dirname(os.path.abspath(__file__)) CARRIER = os.path.join(HERE, "..", "carrier") def U(label): return str(uuidlib.uuid5(uuidlib.NAMESPACE_DNS, "cm5carrier-power-" + label)) SHEET_SYMBOL_UUID = U("sheet-symbol") SHEET_FILE_UUID = U("sheet-file") def g(v): return round(round(v / 1.27) * 1.27, 2) def paren_end(t, s): d = 0 for i in range(s, len(t)): if t[i] == '(': d += 1 elif t[i] == ')': d -= 1 if d == 0: return i + 1 raise ValueError("unbalanced") def get_symbol(text, name): m = re.search(r'\(symbol\s+"' + re.escape(name) + r'"', text) return text[m.start():paren_end(text, m.start())] if m else None def lib_pins(seg): out = {} for pm in re.finditer(r'\(pin\b', seg): s = pm.start(); ps = seg[s:paren_end(seg, s)] at = re.search(r'\(at\s+([\d.-]+)\s+([\d.-]+)\s+([\d.-]+)\)', ps) nu = re.search(r'\(number\s+"([^"]+)"', ps) if at and nu: out.setdefault(nu.group(1), (float(at.group(1)), float(at.group(2)), float(at.group(3)))) return out # ---- lib_symbol sources ----------------------------------------------------------- carrier_hs = open(os.path.join(CARRIER, "CM5_HighSpeed.kicad_sch")).read() LIBS = {} # lib_id -> (embed_block, {pin:(lx,ly,ang)}) def add_from(lib_id, srctext, srcname=None): srcname = srcname or lib_id.split(':')[-1] blk = get_symbol(srctext, lib_id) or get_symbol(srctext, srcname) if not blk: sys.exit("missing lib_symbol " + lib_id) if ('"' + lib_id + '"') not in blk[:60]: blk = blk.replace('(symbol "' + srcname + '"', '(symbol "' + lib_id + '"', 1) LIBS[lib_id] = (blk, lib_pins(blk)) # reuse R/C/C_Polarized/GND verbatim from the carrier (guaranteed compatible) for lid in ["Device:R", "Device:C", "Device:C_Polarized", "power:GND"]: add_from(lid, carrier_hs) add_from("Device:L", open(f"{KSS}/Device.kicad_sym").read(), "L") def register_lm61460(): """Hand-authored lib_symbol for LM61460-Q1 (14-pin HTSSOP-1EP + EP=15), built from the datasheet pin table (SNVSB70F). VREF(FB)=1.0V, VIN abs-max 42V. Pin types picked for cross-sheet ERC: VIN/PGND/AGND/BIAS/EP power_in, VCC power_out, SW output, FB/EN input, RT/CBOOT/RBOOT passive, PGOOD open_collector.""" P = [('8','VIN1','power_in',-16.51,15.24,0), ('12','VIN2','power_in',-16.51,12.7,0), ('7','EN/SYNC','input',-16.51,5.08,0), ('1','BIAS','power_in',-16.51,0,0), ('6','RT','passive',-16.51,-7.62,0), ('10','SW','output',16.51,15.24,180), ('14','CBOOT','passive',16.51,12.7,180), ('13','RBOOT','passive',16.51,10.16,180), ('2','VCC','power_out',16.51,5.08,180), ('4','FB','passive',16.51,-2.54,180), ('5','PGOOD','open_collector',16.51,-7.62,180), ('9','PGND1','power_in',-7.62,-21.59,90), ('11','PGND2','power_in',-2.54,-21.59,90), ('3','AGND','power_in',2.54,-21.59,90), ('15','EP','power_in',7.62,-21.59,90)] pt = "" for num, name, ty, lx, ly, ang in P: pt += (f'\t\t\t(pin {ty} line (at {lx} {ly} {ang}) (length 3.81)\n' f'\t\t\t\t(name "{name}" (effects (font (size 1.27 1.27))))\n' f'\t\t\t\t(number "{num}" (effects (font (size 1.27 1.27))))\n\t\t\t)\n') blk = ('\t(symbol "CM5IO:LM61460-Q1"\n\t\t(pin_names (offset 1.016))\n' '\t\t(exclude_from_sim no)\n\t\t(in_bom yes)\n\t\t(on_board yes)\n' '\t\t(property "Reference" "U" (at 0 22.86 0) (effects (font (size 1.27 1.27))))\n' '\t\t(property "Value" "LM61460-Q1" (at 0 -26.67 0) (effects (font (size 1.27 1.27))))\n' '\t\t(property "Footprint" "Package_SO:Texas_HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3.155x3.255mm_ThermalVias" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' '\t\t(property "Datasheet" "https://www.ti.com/lit/ds/symlink/lm61460-q1.pdf" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' '\t\t(symbol "LM61460-Q1_0_1"\n' '\t\t\t(rectangle (start -12.7 20.32) (end 12.7 -20.32) (stroke (width 0.254) (type default)) (fill (type background)))\n\t\t)\n' f'\t\t(symbol "LM61460-Q1_1_1"\n{pt}\t\t)\n\t)') LIBS["CM5IO:LM61460-Q1"] = (blk, lib_pins(blk)) register_lm61460() # +5v pre-exists in the carrier as a HIERARCHICAL net (local + hierarchical labels on the CM5 # sheets) -> expose it hierarchically here (a global label collides: same_local_global_label). # +3V3_RF/+3V3_AUX are new nets introduced by this port -> global is fine. GLOBAL_NETS = {"+3V3_RF", "+3V3_AUX"} HIER_NETS = {"+5v"} # from KiCad libs (renamed to full lib_id) add_from("power:PWR_FLAG", open(f"{KSS}/power.kicad_sym").read(), "PWR_FLAG") add_from("Power_Management:LM74700", open(f"{KSS}/Power_Management.kicad_sym").read(), "LM74700") add_from("Transistor_FET:Q_NMOS_GDS", open(f"{KSS}/Transistor_FET.kicad_sym").read(), "Q_NMOS_GDS") add_from("Device:D_TVS", open(f"{KSS}/Device.kicad_sym").read(), "D_TVS") add_from("Connector:Screw_Terminal_01x02", open(f"{KSS}/Connector.kicad_sym").read(), "Screw_Terminal_01x02") # ---- placement engine ------------------------------------------------------------- body = [] def emit_label(net, x, y, ang=0): body.append(f'\t(label "{net}"\n\t\t(at {x} {y} {ang})\n' f'\t\t(effects (font (size 1.27 1.27)) (justify left))\n\t\t(uuid "{U("l-"+net+f"-{x}-{y}")}")\n\t)\n') def emit_glabel(net, x, y, ang=0): body.append(f'\t(global_label "{net}"\n\t\t(shape input)\n\t\t(at {x} {y} {ang})\n' f'\t\t(effects (font (size 1.27 1.27)) (justify left))\n\t\t(uuid "{U("gl-"+net+f"-{x}-{y}")}")\n\t)\n') def emit_hlabel(net, x, y, ang=0): body.append(f'\t(hierarchical_label "{net}"\n\t\t(shape passive)\n\t\t(at {x} {y} {ang})\n' f'\t\t(effects (font (size 1.27 1.27)) (justify left))\n\t\t(uuid "{U("hl-"+net+f"-{x}-{y}")}")\n\t)\n') def emit_gnd(x, y, tag): body.append(f'\t(symbol\n\t\t(lib_id "power:GND")\n\t\t(at {x} {y} 0)\n\t\t(unit 1)\n' f'\t\t(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)\n\t\t(uuid "{U("gnd-"+tag)}")\n' f'\t\t(property "Reference" "#PWR{tag}" (at {x} {y+3} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(property "Value" "GND" (at {x} {y+2} 0) (effects (font (size 1.27 1.27))))\n' f'\t\t(property "Footprint" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(property "Datasheet" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(pin "1" (uuid "{U("gp-"+tag)}"))\n' f'\t\t(instances (project "{PROJECT}" (path "/{ROOT_UUID}/{SHEET_SYMBOL_UUID}" (reference "#PWR{tag}") (unit 1))))\n\t)\n') def emit_flag(x, y, tag): body.append(f'\t(symbol\n\t\t(lib_id "power:PWR_FLAG")\n\t\t(at {x} {y} 0)\n\t\t(unit 1)\n' f'\t\t(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)\n\t\t(uuid "{U("flg-"+tag)}")\n' f'\t\t(property "Reference" "#FLG{tag}" (at {x} {y-3} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(property "Value" "PWR_FLAG" (at {x} {y-2} 0) (effects (font (size 1.27 1.27))))\n' f'\t\t(property "Footprint" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(property "Datasheet" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(pin "1" (uuid "{U("fp-"+tag)}"))\n' f'\t\t(instances (project "{PROJECT}" (path "/{ROOT_UUID}/{SHEET_SYMBOL_UUID}" (reference "#FLG{tag}") (unit 1))))\n\t)\n') def place(lib_id, ref, value, footprint, ox, oy, pin_nets): """Place a symbol at grid-snapped (ox,oy); attach each pin's net by name. pin_nets: {pinnum: 'NET' | ('gnd',) }. GND pins use a power:GND symbol.""" ox, oy = g(ox), g(oy) _, pins = LIBS[lib_id] extra = ('\t\t(property "Description" "" (at %g %g 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' % (ox, oy)) sym = (f'\t(symbol\n\t\t(lib_id "{lib_id}")\n\t\t(at {ox} {oy} 0)\n\t\t(unit 1)\n' f'\t\t(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)\n\t\t(uuid "{U("sym-"+ref)}")\n' f'\t\t(property "Reference" "{ref}" (at {ox+2.54} {oy-1} 0) (effects (font (size 1.27 1.27)) (justify left)))\n' f'\t\t(property "Value" "{value}" (at {ox+2.54} {oy+1} 0) (effects (font (size 1.27 1.27)) (justify left)))\n' f'\t\t(property "Footprint" "{footprint}" (at {ox} {oy} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' f'\t\t(property "Datasheet" "" (at {ox} {oy} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' + extra) for num in sorted(pins, key=lambda n: int(re.sub(r'\D', '', n) or 0)): sym += f'\t\t(pin "{num}" (uuid "{U("p-"+ref+"-"+num)}"))\n' sym += f'\t\t(instances (project "{PROJECT}" (path "/{ROOT_UUID}/{SHEET_SYMBOL_UUID}" (reference "{ref}") (unit 1))))\n\t)\n' body.append(sym) for num, net in pin_nets.items(): lx, ly, _ = pins[num]; px, py = round(ox + lx, 2), round(oy - ly, 2) if net == ('gnd',): emit_gnd(px, py, ref + num) elif isinstance(net, tuple) and net[0] == 'global': emit_glabel(net[1], px, py) elif net in GLOBAL_NETS: emit_glabel(net, px, py) elif net in HIER_NETS: emit_hlabel(net, px, py) else: emit_label(net, px, py) return ox, oy # ---- STAGE 1: input protection ---------------------------------------------------- GND = ('gnd',) place("Connector:Screw_Terminal_01x02", "J_PWR", "15V_DC_IN", "TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-2_1x02_P5.00mm_Horizontal", 80, 80, {'1': '+15V_IN', '2': GND}) drev = place("Power_Management:LM74700", "D_REV", "LM74700-Q1", "Package_TO_SOT_SMD:SOT-23-6", 120, 80, {'6': '+15V_IN', '3': '+15V_IN', '4': 'VIN_PROT', '5': 'GATE_REV', '1': 'VCAP_REV', '2': GND}) place("Transistor_FET:Q_NMOS_GDS", "Q_REV", "CSD18540 (Vds>=30V)", "Package_TO_SOT_SMD:TO-263-2", 140, 90, {'1': 'GATE_REV', '2': 'VIN_PROT', '3': '+15V_IN'}) place("Device:C", "C_VCAP", "1u", "Capacitor_SMD:C_0402_1005Metric", 120, 100, {'1': 'VCAP_REV', '2': GND}) tvs = place("Device:D_TVS", "TVS_IN", "SMBJ18A", "Diode_SMD:D_SMB", 160, 80, {'1': 'VIN_PROT', '2': GND}) place("Device:C_Polarized", "C_BULK1", "100u/25V", "Capacitor_SMD:CP_Elec_8x10", 170, 80, {'1': 'VIN_PROT', '2': GND}) place("Device:C", "C_BULK2", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 180, 80, {'1': 'VIN_PROT', '2': GND}) # PWR_FLAGs co-located exactly on real pins so they bind: +15V_IN @ D_REV.6 (ANODE), # VIN_PROT @ TVS.1 (a power_in driver for VIN_PROT, needed once bucks consume it). def pin_abs(origin, lib_id, num): lx, ly, _ = LIBS[lib_id][1][num] return round(origin[0] + lx, 2), round(origin[1] - ly, 2) fx, fy = pin_abs(drev, "Power_Management:LM74700", "6"); emit_flag(fx, fy, "15IN") vx, vy = pin_abs(tvs, "Device:D_TVS", "1"); emit_flag(vx, vy, "VINP") # ---- STAGE 2: U_BUCK5 15V -> +5v 5A (LM61460-Q1, sync, 42V absmax) -------------- # FB divider: Vout = VREF*(1+Rfbt/Rfbb), VREF=1.0V -> Rfbt/Rfbb=4 -> 40.2k/10.0k = 5.02V. # fsw ~= 400kHz (RT 31.6k); L=4.7uH; Cout 2x47u/16V; Cin 2x10u/50V; Cboot 100n; BIAS->+5v. place("CM5IO:LM61460-Q1", "U_BUCK5", "LM61460-Q1", "Package_SO:Texas_HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3.155x3.255mm_ThermalVias", 110, 150, {'8': 'VIN_PROT', '12': 'VIN_PROT', '7': 'VIN_PROT', '1': '+5v', '2': 'VCC5', '6': 'RT5', '5': 'PG5', '4': 'FB5', '10': 'SW5', '14': 'BOOT5', '13': 'RBOOT5', '9': GND, '11': GND, '3': GND, '15': GND}) l5 = place("Device:L", "L5", "4.7uH/7A", "Inductor_SMD:L_Bourns_SRP1245A", 150, 138, {'1': 'SW5', '2': '+5v'}) place("Device:C", "C_BOOT5", "100n", "Capacitor_SMD:C_0402_1005Metric", 140, 132, {'1': 'SW5', '2': 'BOOT5'}) place("Device:R", "R_BOOT5", "4.7", "Resistor_SMD:R_0402_1005Metric", 145, 128, {'1': 'RBOOT5', '2': 'BOOT5'}) place("Device:R", "R_RT5", "31.6k", "Resistor_SMD:R_0402_1005Metric", 90, 158, {'1': 'RT5', '2': GND}) place("Device:R", "R_PG5", "100k", "Resistor_SMD:R_0402_1005Metric", 135, 143, {'1': 'PG5', '2': '+5v'}) place("Device:R", "R_FBT5", "40.2k", "Resistor_SMD:R_0402_1005Metric", 165, 150, {'1': '+5v', '2': 'FB5'}) place("Device:R", "R_FBB5", "10.0k", "Resistor_SMD:R_0402_1005Metric", 165, 158, {'1': 'FB5', '2': GND}) place("Device:C", "C_VCC5", "1u", "Capacitor_SMD:C_0402_1005Metric", 100, 158, {'1': 'VCC5', '2': GND}) place("Device:C", "C_IN5a", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 95, 145, {'1': 'VIN_PROT', '2': GND}) place("Device:C", "C_IN5b", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 100, 145, {'1': 'VIN_PROT', '2': GND}) place("Device:C", "C_OUT5a", "47u/16V", "Capacitor_SMD:C_1210_3225Metric", 175, 138, {'1': '+5v', '2': GND}) place("Device:C", "C_OUT5b", "47u/16V", "Capacitor_SMD:C_1210_3225Metric", 180, 138, {'1': '+5v', '2': GND}) # +5v is now sourced by this buck -> PWR_FLAG co-located on L5 pin2 (+5v) p5x, p5y = pin_abs(l5, "Device:L", "2"); emit_flag(p5x, p5y, "P5V") # ---- file scaffold ---------------------------------------------------------------- libsyms = "\n".join(blk for blk, _ in LIBS.values()) sheet = ('(kicad_sch\n\t(version 20231120)\n\t(generator "eeschema")\n\t(generator_version "8.0")\n' f'\t(uuid "{SHEET_FILE_UUID}")\n\t(paper "A4")\n' '\t(title_block\n\t\t(title "CM5 Carrier - Power")\n\t\t(rev "A")\n' '\t\t(comment 1 "15V inlet -> ideal-diode -> VIN_PROT -> 3 bucks (+5V, +3V3_RF, +3V3_AUX)")\n\t)\n' '\t(lib_symbols\n' + libsyms + '\n\t)\n' + "".join(body) + '\t(sheet_instances\n\t\t(path "/"\n\t\t\t(page "5")\n\t\t)\n\t)\n)\n') assert sheet.count('(') == sheet.count(')'), f"UNBALANCED {sheet.count('(')} vs {sheet.count(')')}" print("sheet-symbol uuid:", SHEET_SYMBOL_UUID) print("parens balanced:", sheet.count('(')) out = os.path.join(CARRIER, "Power.kicad_sch") if '--apply' in sys.argv: open(out, 'w').write(sheet); print("WROTE " + out) else: print("DRY RUN (--apply to write)")