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
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
#!/usr/bin/env python3
"""Last-resort width repair: step a track down until it clears exactly.
The widening pass settles against its own model of the board. Anything that
survives it is a pair the model and the rule disagree about by a hair, and the
cheapest correct answer is to give back a ladder step of width on the offending
track rather than re-route anything.
Reads the whole file before opening it for writing -- `open(p,"w").write(...)`
truncates the file before the expression inside it is evaluated, which once
turned a fully routed board into zero bytes.
"""
import re, sys
import geomcheck as G
PCB = ("/mnt/c/Users/kirkt/Documents/mppt-power-system-mcu/"
"mppt-power-system-mcu.kicad_pcb")
LADDER = [1.2, 1.0, 0.8, 0.6, 0.5, 0.4, 0.3, 0.25, 0.2, 0.15]
PAT = re.compile(
r'(\(segment\s+\(start ([-\d.]+) ([-\d.]+)\)\s+\(end ([-\d.]+) ([-\d.]+)\)'
r'\s+\(width )([\d.]+)(\)\s+\(layer "([^"]+)"\))')
def main(path=PCB, rounds=8):
for it in range(rounds):
objs = G.load_board(path)
if not objs:
print(f"ERROR: {path} parsed to zero objects"); return 1
v = G.violations(objs)
print(f"pass {it}: {len(v)} exact violation(s)")
if not v:
return 0
fixes = {}
for gap, rule, why, a, b in v:
print(f" gap {gap:+.3f}/{rule} "
f"{a.kind}[{a.net}]{'@' + a.tag if a.tag else ''} vs "
f"{b.kind}[{b.net}]{'@' + b.tag if b.tag else ''}")
for o in (a, b):
if o.kind != "track":
continue
w = o.r * 2
lower = [x for x in LADDER if x < w - 1e-9]
if not lower:
continue
fixes[(round(o.ax, 4), round(o.ay, 4), round(o.bx, 4),
round(o.by, 4), o.layers[0], w)] = lower[0]
if not fixes:
print(" nothing left to narrow -- these need a re-route")
return len(v)
text = open(path, errors='replace').read() # read BEFORE opening 'w'
n = [0]
def sub(m):
k = (round(float(m.group(2)), 4), round(float(m.group(3)), 4),
round(float(m.group(4)), 4), round(float(m.group(5)), 4),
m.group(8), float(m.group(6)))
if k in fixes:
n[0] += 1
return f"{m.group(1)}{fixes[k]:g}{m.group(7)}"
return m.group(0)
out = PAT.sub(sub, text)
if n[0] == 0:
print(" no segment text matched -- stopping"); return len(v)
with open(path, "w") as fh:
fh.write(out)
print(f" narrowed {n[0]} segment(s)")
return 0
if __name__ == "__main__":
sys.exit(main())