board
Solar MPPT Power System
Public Unreviewedby TylerK
A 30 W solar battery charger with regulated 5 V and 3.3 V rails: an STM32G031 running perturb & observe MPPT and a CC/CV charge state machine, steering a TI LM5175 4-switch buck-boost through a single analog control input. Four-layer KiCad board, schematic and PCB generated from Python, with an electrothermal simulation run against the published board file.
main
TylerK
Board update: 4 unconnected items (from 13), 0 electrical and 0 mechanical DRC findings, silkscreen findings 73 -> 10;…
337e32d
1mo ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
#!/usr/bin/env python3
"""One-command board verdict: runs KiCad DRC with zone refill, then classifies
every finding as ELECTRICAL (must fix), MECHANICAL (should fix) or COSMETIC.
Exists because sampling a DRC report by eye is how 86 genuine different-net
shorts got mischaracterised as harmless same-net warnings earlier in this
project. Every finding is now classified programmatically, and the
different-net check is explicit rather than assumed.
"""
import re, subprocess, sys
from collections import Counter
KICAD = "/mnt/c/Program Files/KiCad/10.0/bin/kicad-cli.exe"
PCB_WIN = r"C:\Users\kirkt\Documents\mppt-power-system-mcu\mppt-power-system-mcu.kicad_pcb"
RPT_WIN = r"C:\Users\kirkt\Documents\mppt-power-system-mcu\drc_check.rpt"
RPT = "/mnt/c/Users/kirkt/Documents/mppt-power-system-mcu/drc_check.rpt"
ELECTRICAL = {"shorting_items", "clearance", "tracks_crossing", "unconnected_items",
"hole_clearance", "solder_mask_bridge", "starved_thermal"}
MECHANICAL = {"hole_to_hole", "holes_co_located", "annular_width", "track_dangling",
"isolated_copper", "courtyards_overlap", "malformed_courtyard"}
COSMETIC = {"silk_overlap", "silk_over_copper", "lib_footprint_mismatch",
"footprint_symbol_mismatch", "extra_footprint", "silk_edge_clearance"}
def nets_in(entry):
"""Net names referenced by the two offending items."""
out = []
for line in entry.split("\n")[1:]:
m = re.search(r'\[([^\]]+)\]', line)
if m: out.append(m.group(1))
return out
def main(refill=True):
cmd = [KICAD, "pcb", "drc"]
if refill: cmd += ["--refill-zones", "--save-board"]
cmd += [PCB_WIN, "-o", RPT_WIN]
r = subprocess.run(cmd, capture_output=True, text=True)
head = [l for l in (r.stdout or "").splitlines() if "Found" in l]
txt = open(RPT, errors="replace").read()
entries = re.split(r'\n(?=\[\w+\]:)', txt)
kinds = Counter()
diff_net = Counter()
same_net = Counter()
worst = []
for e in entries:
m = re.match(r'\[(\w+)\]', e)
if not m: continue
k = m.group(1); kinds[k] += 1
ns = set(nets_in(e))
if k in ELECTRICAL and k != "unconnected_items":
if len(ns) > 1:
diff_net[k] += 1
a = re.search(r'actual ([\d.]+) mm', e)
worst.append((float(a.group(1)) if a else 0.0, k, sorted(ns)))
else:
same_net[k] += 1
e_tot = sum(v for k, v in kinds.items() if k in ELECTRICAL)
m_tot = sum(v for k, v in kinds.items() if k in MECHANICAL)
c_tot = sum(v for k, v in kinds.items() if k in COSMETIC)
other = sum(v for k, v in kinds.items()
if k not in ELECTRICAL | MECHANICAL | COSMETIC)
for l in head: print(l)
print(f"\n ELECTRICAL (must fix) : {e_tot}")
for k in sorted(ELECTRICAL):
if kinds.get(k):
d = diff_net.get(k, 0); s = same_net.get(k, 0)
extra = f" [different-net: {d}, same-net: {s}]" if k != "unconnected_items" else ""
print(f" {k:22s} {kinds[k]:4d}{extra}")
print(f" MECHANICAL (should fix): {m_tot}")
for k in sorted(MECHANICAL):
if kinds.get(k): print(f" {k:22s} {kinds[k]:4d}")
print(f" COSMETIC : {c_tot}")
for k in sorted(COSMETIC):
if kinds.get(k): print(f" {k:22s} {kinds[k]:4d}")
if other: print(f" UNCLASSIFIED : {other}")
real = diff_net_total = sum(diff_net.values())
unconn = kinds.get("unconnected_items", 0)
print(f"\n >>> genuine different-net electrical defects: {real}")
print(f" >>> unconnected items: {unconn}")
if worst:
worst.sort()
print("\n tightest different-net clearances:")
for a, k, ns in worst[:8]:
print(f" {a:.3f}mm {k:18s} {ns}")
return real + unconn
if __name__ == "__main__":
sys.exit(0 if main() == 0 else 1)