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.
← Commit history
kicad/generator-scripts/schgen.pydeleted−145@@ -1,145 +0,0 @@-#!/usr/bin/env python3-"""Generator core for the discrete-analog MPPT solar charge system schematic.-Mirrors the proven Sch/SymLib approach from the earlier Multisim-conversion project."""-import re, uuid as U-from symlib import SymLib--PROJECT = "mppt-power-system"-GRID = 1.27-def snap(v): return round(v / GRID) * GRID-def uid(): return str(U.uuid4())--LIBS = ['Amplifier_Operational.kicad_sym', 'Comparator.kicad_sym', 'Reference_Voltage.kicad_sym',- 'Timer.kicad_sym', 'Transistor_BJT.kicad_sym', 'Transistor_FET.kicad_sym',- 'Diode.kicad_sym', 'Device.kicad_sym', 'power.kicad_sym', 'Connector_Generic.kicad_sym',- 'Regulator_Linear.kicad_sym']--class Sch:- def __init__(self, lib):- self.lib = lib- self.items = []- self.used = {}- self.root = uid()- self.net_pins = {} # net name -> [(ref, pinname)] for a summary report-- def need(self, l, n):- self.used.setdefault((l, n), self.lib.lib_symbol_entry(l, n))-- def _pin_lookup(self, l, n, unit):- return self.lib.pins(l, n).get(unit, [])-- def place(self, l, n, unit, ref, value, x, y, rot=0, mirror=None, footprint='',- nets=None, hide_val=False, extra_props=None):- """nets: {pin_number_or_name: net_name}. Matches by number first, then by name."""- self.need(l, n)- x, y = snap(x), snap(y)- pu = uid()- props = [('Reference', ref, False), ('Value', value, hide_val),- ('Footprint', footprint, True), ('Datasheet', '', True)]- if extra_props:- props += [(k, v, True) for k, v in extra_props.items()]- s = ['\t(symbol', f'\t\t(lib_id "{l}:{n}")', f'\t\t(at {x:.2f} {y:.2f} {rot})']- if mirror:- s.append(f'\t\t(mirror {mirror})')- s += [f'\t\t(unit {unit})', '\t\t(body_style 1)', '\t\t(exclude_from_sim no)',- '\t\t(in_bom yes)', '\t\t(on_board yes)', '\t\t(in_pos_files yes)',- '\t\t(dnp no)', f'\t\t(uuid "{pu}")']- py = y - 10.0- for name, v, hide in props:- esc = v.replace('\\', '\\\\').replace('"', '\\"')- s.append(f'\t\t(property "{name}" "{esc}"')- s.append(f'\t\t\t(at {x:.2f} {py:.2f} 0)')- if hide: s.append('\t\t\t(hide yes)')- s.append('\t\t\t(show_name no)\n\t\t\t(do_not_autoplace no)')- s.append('\t\t\t(effects\n\t\t\t\t(font\n\t\t\t\t\t(size 1.0 1.0)\n\t\t\t\t)\n\t\t\t)')- s.append('\t\t)')- py += 1.8- pins = self._pin_lookup(l, n, unit)- nets = nets or {}- for num, pname, *_ in pins:- self.need(l, n)- for num, pname, *_ in pins:- s.append(f'\t\t(pin "{num}"\n\t\t\t(uuid "{uid()}")\n\t\t)')- s.append('\t\t(instances')- s.append(f'\t\t\t(project "{PROJECT}"')- s.append(f'\t\t\t\t(path "/{self.root}"')- s.append(f'\t\t\t\t\t(reference "{ref}")\n\t\t\t\t\t(unit {unit})\n\t\t\t\t)')- s.append('\t\t\t)\n\t\t)\n\t)')- self.items.append('\n'.join(s))- # emit labels for every requested net (by pin number OR pin name)- by_num = {num: (num, pname, et, px, py2, ang) for num, pname, et, px, py2, ang in pins}- by_name = {}- for num, pname, et, px, py2, ang in pins:- if pname: by_name.setdefault(pname, (num, pname, et, px, py2, ang))- for key, netname in nets.items():- entry = by_num.get(str(key)) or by_name.get(key)- if entry is None:- raise KeyError(f"{ref}: no pin '{key}' on {l}:{n} unit {unit} (have {[p[0:2] for p in pins]})")- num, pname, et, px, py2, ang = entry- ax, ay = x + px, y - py2- rot_lbl, just = self._label_geom(px, py2, ang)- self.label(netname, ax, ay, rot_lbl, just)- self.net_pins.setdefault(netname, []).append((ref, pname or num))-- @staticmethod- def _label_geom(px, py, ang):- if ang == 0: return 0, 'right bottom'- if ang == 180: return 0, 'left bottom'- if ang == 90: return 90, 'right bottom'- return 90, 'left bottom'-- def label(self, text, x, y, rot, just):- x, y = snap(x), snap(y)- esc = text.replace('\\', '\\\\').replace('"', '\\"')- self.items.append(- f'\t(label "{esc}"\n\t\t(at {x:.2f} {y:.2f} {rot})\n'- f'\t\t(effects\n\t\t\t(font\n\t\t\t\t(size 1.0 1.0)\n\t\t\t)\n'- f'\t\t\t(justify {just})\n\t\t)\n\t\t(uuid "{uid()}")\n\t)')-- def noconn(self, l, n, unit, x, y, pin_key):- pins = self._pin_lookup(l, n, unit)- by_num = {num: (num, pname, et, px, py2, ang) for num, pname, et, px, py2, ang in pins}- by_name = {}- for num, pname, et, px, py2, ang in pins:- if pname: by_name.setdefault(pname, (num, pname, et, px, py2, ang))- entry = by_num.get(str(pin_key)) or by_name.get(pin_key)- num, pname, et, px, py2, ang = entry- ax, ay = snap(x + px), snap(y - py2)- self.items.append(f'\t(no_connect\n\t\t(at {ax:.2f} {ay:.2f})\n\t\t(uuid "{uid()}")\n\t)')-- def text(self, t, x, y, size=2.5, bold=True):- esc = t.replace('"', '\\"')- b = '\n\t\t\t\t(bold yes)' if bold else ''- self.items.append(- f'\t(text "{esc}"\n\t\t(exclude_from_sim no)\n\t\t(at {x:.2f} {y:.2f} 0)\n'- f'\t\t(effects\n\t\t\t(font\n\t\t\t\t(size {size} {size}){b}\n\t\t\t)\n'- f'\t\t\t(justify left bottom)\n\t\t)\n\t\t(uuid "{uid()}")\n\t)')-- def rect_box(self, x0, y0, x1, y1):- self.items.append(- f'\t(rectangle\n\t\t(start {x0:.2f} {y0:.2f})\n\t\t(end {x1:.2f} {y1:.2f})\n'- f'\t\t(stroke\n\t\t\t(width 0.25)\n\t\t\t(type dash)\n\t\t)\n'- f'\t\t(fill\n\t\t\t(type none)\n\t\t)\n\t\t(uuid "{uid()}")\n\t)')-- def render(self, w, h, title, rev, company, date):- out = ['(kicad_sch', '\t(version 20260306)', '\t(generator "eeschema")',- '\t(generator_version "10.0")', f'\t(uuid "{self.root}")',- f'\t(paper "User" {w} {h})', '\t(title_block',- f'\t\t(title "{title}")', f'\t\t(date "{date}")', f'\t\t(rev "{rev}")',- f'\t\t(company "{company}")', '\t)', '\t(lib_symbols']- out += list(self.used.values()); out.append('\t)')- out += self.items- out.append('\t(sheet_instances\n\t\t(path "/"\n\t\t\t(page "1")\n\t\t)\n\t)')- out.append('\t(embedded_fonts no)\n)')- return '\n'.join(out) + '\n'---def check_balance(text):- d, ins, esc = 0, False, False- for ch in text:- if esc: esc = False; continue- if ch == '\\': esc = True; continue- if ch == '"': ins = not ins; continue- if ins: continue- d += (ch == '(') - (ch == ')')- return d == 0