Power sheet stage 1: 15V input protection (ideal-diode + TVS)
First stage of the power tree. New carrier/Power.kicad_sch (generated by tools/build_power.py): J_PWR 15V inlet -> D_REV LM74700-Q1 ideal-diode controller + Q_REV N-FET (reverse-polarity / reverse-current) -> VIN_PROT, with SMBJ18A input TVS and 100uF/25V || 10uF/50V bulk + VCAP cap. Part-selection note: the spec's buck candidates miss its own >=20V Vin rule (TPS54424=17V, TPS54560=async); the 3 sync bucks (stages 2-4) will use >=30V synchronous parts, datasheet-verified, feeding +5V/+3V3_RF/+3V3_AUX. tools/build_power.py generalizes the build_ekey approach: generic place() attaches a net label / GND symbol per pin (by-name connectivity, grid-snapped); PWR_FLAGs co-located on real pin coords. Verify: ERC 133->133 (zero new); analyzer +16 added, 0 regressions; netlist confirms the ideal-diode topology (+15V_IN/VIN_PROT/GATE_REV/VCAP_REV/GND). Confirm vs LM74700-Q1 datasheet: C_VCAP value, EN tie, FET Vds/SOA, gate R. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,10 +97,11 @@ Notes that bit us (don't re-learn the hard way):
|
||||
power sheet; (b) the temp PWR_FLAG on +3V3_RF must be REMOVED once the buck drives it (else dual-driver ERC);
|
||||
(c) optionally route `WIFI_DISABLE#` to a CM5 GPIO for SW radio control; (d) analyzer CG-AUD 5:1 sig/gnd on J_E
|
||||
is inherent to the M.2 connector (INFO, not fixable). New tool: `tools/build_ekey.py` (place connector + auto-NC + labels).
|
||||
4. **Remaining add phase**: 15V→5V buck front-end (reverse-protection + TVS); the two 3.3V rails
|
||||
(`+3V3_RF` for the E-key — wire its buck EN to CM5 `PCIE_PWR_EN`/106 and drop the temp PWR_FLAG —
|
||||
and `+3V3_AUX` for GPS/IO); GPS connector; PPS distribution (GPS PPS → TVS → Schmitt buffer →
|
||||
fan-out: CM5 `Ethernet_SYNC_OUT` + 2 header taps). SPICE-validate the buck divider + PPS network.
|
||||
4. **Power sheet** (`carrier/Power.kicad_sch`, generated by `tools/build_power.py`) — staged, **sync + datasheet-built parts** (user choice). **Spec rating fix:** the spec's buck candidates miss its own ≥20V-Vin rule (TPS54424=17V, TPS54560=async) — selecting synchronous ≥30V parts instead (LM61460-class 6A for +5V & +3V3_RF/4A; TPS54202 28V for +3V3_AUX), each verified vs datasheet before commit.
|
||||
- **Stage 1 — input protection ✅ DONE.** J_PWR(15V) → D_REV `LM74700-Q1` ideal-diode + Q_REV N-FET (reverse-polarity/-current) → `VIN_PROT`; `SMBJ18A` TVS + 100µF/25V ∥ 10µF/50V bulk; C_VCAP charge-pump cap. Netlist-verified topology; ERC 133→133 (zero new); analyzer 0 regressions. **Confirm vs LM74700-Q1 datasheet:** C_VCAP value, EN-to-+15V_IN, FET pick (Vds≥30V, SOA/inrush) + optional gate R.
|
||||
- **Stage 2** U_BUCK5 15→5V 5A → `+5V`. **Stage 3** U_BUCK33R 15→3.3V 4A → `+3V3_RF` (then REMOVE the E-key temp PWR_FLAG; wire buck EN ← CM5 `PCIE_PWR_EN`/106). **Stage 4** U_BUCK33A 15→3.3V 1A → `+3V3_AUX`. SPICE-validate each feedback divider (like the reference U8/AP3441).
|
||||
5. **GPS + PPS distribution**: GPS connector (PPS+UART+3V3+GND); PPS → TVS → Schmitt (LVC1G17) →
|
||||
fan-out: CM5 `Ethernet_SYNC_OUT` (J2 pin-6 net, 3.3V, no translator) + 2 header taps. SPICE/timing check.
|
||||
|
||||
## Out-of-repo notes
|
||||
|
||||
|
||||
@@ -837,6 +837,10 @@
|
||||
[
|
||||
"c57f5f03-e2c5-5c1b-ba90-8072a2a088a0",
|
||||
"M2_Ekey"
|
||||
],
|
||||
[
|
||||
"9c7ccf72-dd3e-5ed3-bb43-2939833eb069",
|
||||
"Power"
|
||||
]
|
||||
],
|
||||
"text_variables": {}
|
||||
|
||||
@@ -3469,6 +3469,16 @@
|
||||
(property "Sheetfile" "M2_Ekey.kicad_sch" (at 205 31.75 0) (effects (font (size 1.27 1.27)) (justify left top)))
|
||||
(instances (project "CM5IO" (path "/e63e39d7-6ac0-4ffd-8aa3-1841a4541b55" (page "4"))))
|
||||
)
|
||||
(sheet
|
||||
(at 205 60)
|
||||
(size 30 20)
|
||||
(stroke (width 0.1524) (type solid) (color 132 0 132 1))
|
||||
(fill (color 255 255 255 0.0000))
|
||||
(uuid "9c7ccf72-dd3e-5ed3-bb43-2939833eb069")
|
||||
(property "Sheetname" "Power" (at 205 56.18 0) (effects (font (size 1.27 1.27)) (justify left bottom)))
|
||||
(property "Sheetfile" "Power.kicad_sch" (at 205 57.45 0) (effects (font (size 1.27 1.27)) (justify left top)))
|
||||
(instances (project "CM5IO" (path "/e63e39d7-6ac0-4ffd-8aa3-1841a4541b55" (page "5"))))
|
||||
)
|
||||
(sheet_instances
|
||||
(path "/"
|
||||
(page "1")
|
||||
|
||||
1803
carrier/Power.kicad_sch
Normal file
1803
carrier/Power.kicad_sch
Normal file
File diff suppressed because it is too large
Load Diff
160
tools/build_power.py
Normal file
160
tools/build_power.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/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)
|
||||
# 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_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)
|
||||
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")
|
||||
|
||||
# ---- 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)")
|
||||
Reference in New Issue
Block a user