← Commit history

Initial commit: component/mppt-power-system

TylerK ·02cae080c4 ·1mo ago
2 files changed +283
README.mdadded+249
@@ -0,0 +1,249 @@+# Solar MPPT Charge System++A 30 W solar battery charger with 5 V / 3.3 V point-of-load rails, built entirely from+op-amps, comparators, timers and discrete FETs — **no microcontroller and no dedicated+MPPT, PWM-controller or current-sense ICs.** Four-layer KiCad board, and both the+schematic and the PCB are emitted programmatically from Python.++---++## The constraint that shaped it++The design brief was "basic components, no separate chips." That was read as: no MPPT+controller, no buck-boost controller IC, no current-sense amplifier IC, no MCU. What is+allowed is the generic, decades-old, multi-sourced stuff — LM324 quad op-amps, LM393+comparators, TL431 shunt references, NE555 timers, zeners, discrete BJTs and power+MOSFETs.++That constraint drives nearly every decision on the board:++- **Buck-only, not buck-boost.** Charging a 14.2 V target needs Vin above roughly+  15–16 V regardless of topology, so a buck-boost's second half-bridge bought nothing+  here. Below ~15 V the converter simply delivers little or no charge current — which is+  how any simple analog solar charge controller behaves, not a fault.+- **3.3 V is linear, from the 5 V rail.** At 1 A that is (5 − 3.3) × 1 = 1.7 W, which is+  thermally fine. Deriving it from the 12 V bus instead would waste 8.7 W, and adding a+  second switcher for 1.7 W was judged unnecessary complexity.+- **Housekeeping bias cannot come from the battery.** The control circuitry has to start+  switching from a fully depleted battery as long as the panel is lit, so it runs from a+  panel-derived NPN emitter-follower zener regulator — an emitter follower rather than a+  bare zener specifically to keep the series resistor's dissipation sane at open-circuit+  panel voltage.++## How the control loop works++With no MCU there is no P&O algorithm. "MPPT" here is an **input-voltage floor**, and it+only engages when the load asks for more than the panel can give:++1. **Vmp loop** — a divider against a 5.1 V zener reference watches panel voltage and+   folds back duty cycle if the panel sags below its maximum-power point (~18.1 V trip).+2. **Current-limit loop** — a 0.05 Ω shunt, amplified ×20 by an LM324 difference stage,+   compared against a zener-derived reference (~2.19 A trip).+3. **Battery CV loop** — a TL431 does double duty as both reference and error amplifier,+   comparing the battery divider against its own 2.5 V reference (14.10 V trip).++All three error voltages are **diode-OR'd into a common node through 1N4148s**, so+whichever loop is most restrictive at any instant governs duty cycle. That combined error+is compared by an LM393 against a ramp — and the ramp is not a separate generator, it is+the **NE555's own timing-capacitor node** used directly, with the 555 free-running at+~50 kHz. The comparator output drives a 2N2222 that pulls the P-channel switch's gate+down, with a 10 kΩ pull-up so the FET is failsafe-OFF before the loop is even running.++The 5 V stage reuses the identical architecture minus the Vmp loop — the battery is a+stiff source, so only output-voltage and current-limit loops are needed.++## The board++![Board top view](docs/board-top.png)++The layout is organised as labelled functional zones, and the silkscreen says so: the+solar input chain runs along the top left (J1 → F1 fuse → Q1 reverse-polarity P-FET),+the two switching power trains sit in the left-centre as horizontal runs, and both+analog control blocks are pushed out to the right-hand third of the board under+"CHARGE STAGE CONTROL (set back from switch node)" and "5V STAGE CONTROL". That setback+is the design's answer to noise coupling: rather than splitting the ground plane into+analog and power islands — a practice modern guidance generally advises against, because+a moat forces return current to detour and can radiate — the control circuitry is simply+placed a real distance away from the fast-edge nodes. Sixteen test points (TP1–TP16) are+banked along the bottom edge, exposing both PWM ramps, both gate drives, both current+monitors and every rail, so the analog loops can actually be probed on a bench. The board+is sparse for its size; at 242 × 152 mm with 121 footprints there is a lot of bare+copper, which is a direct consequence of the generator placing parts on a generous grid.++## Copper routing++![Copper routing, all four layers](docs/routing.png)++Plotted directly from `mppt-power-system.kicad_pcb`: 776 track segments and 150 vias+across four layers. The two power stages' switch loops were hand-placed and hand-routed+first — those are the wide 1.0–1.2 mm runs on F.Cu — and the remaining control-signal+interconnect was autorouted, on the reasoning that loop area only really matters at the+switch node. The default net-class width was raised to 1.0 mm before autorouting, so even+autorouted nets that happen to sit on a higher-current path aren't left at a thin+signal-only default. What the plot also makes visible is that **In1.Cu, described in the+design review as a solid ground plane, is carrying 198 routed segments of its own** —+see the verification notes below.++## The charge-stage switching loop++![Charge stage switching loop close-up](docs/powerstage.png)++The commutation loop is C20 → Q4 (source→drain) → D9 (cathode→anode) → back to C20's+ground. C20 is the small ceramic immediately upstream of Q4's source, placed there so the+high-frequency ripple current the switch demands each cycle is supplied locally rather+than dragged back through the current-sense shunt RS1 or the bulk capacitors C1/C2 some+20–30 mm upstream — pulling it through RS1 would both enlarge the effective loop and+inject switching noise straight onto the MPPT current-sense signal. D9's anode and C20's+ground pin each drop a dedicated via into the inner plane instead of being tied together+by a routed trace, which is the one place the four-layer stack pays for itself in+loop-area terms. The honest limitation, stated plainly in the design review: Q4 is a+through-hole TO-220, chosen for easy heatsinking and hand assembly, and its 2.54 mm pin+pitch puts a hard floor under how tight this loop can get. An all-SMD switch and Schottky+would do better, and that would be a component respin, not a layout tweak.++## Verified specifications++Every number below was read back out of the source files or produced by KiCad 10.0.2,+not taken from the design notes.++| | |+|---|---|+| Board outline | 242 × 152 mm (36 784 mm²) |+| Layers | 4 — F.Cu / In1.Cu / In2.Cu / B.Cu |+| Stackup thickness | 1.26 mm as defined (see notes) |+| Components | 117 schematic references; 121 PCB footprints (those 117 parts plus 4 mounting holes) |+| Pads | 190 SMD, 87 through-hole, 4 NPTH |+| Nets | 58 (53 named + 5 unconnected-pin pseudo-nets) |+| Routing | 776 track segments, 150 vias, **0 unconnected items** |+| Min track width / clearance / drill | 0.20 mm / 0.20 mm / 0.30 mm |+| ERC | **0 violations** |+| DRC | **0 errors**, 44 warnings, 0 schematic-parity issues |+| Source tool | KiCad 10.0.2 |++DRC warnings break down as 16 library-footprint mismatches, 14 silkscreen overlaps,+7 isolated copper islands, 4 dangling tracks and 3 silk-over-copper. None are electrical.++## Key design values++| Parameter | Value | Basis |+|---|---|---|+| Panel | 30 W, 6–24 V survivable, ~15–16 V minimum for useful charging | brief + buck topology limit |+| Battery | 12 V SLA/AGM assumed, 14.10 V CV setpoint | TL431 + 46.4 k / 10 k divider |+| Vmp trip | 18.10 V (adjustable) | 5.1 V zener + 25.5 k / 10 k divider |+| Charge current limit | ~2.19 A | 0.05 Ω shunt, ×20 gain, 15 % over 1.9 A |+| 5 V rail | 5.0 V / 2.0 A, limit ~2.4 A | 0.05 Ω shunt, ×20 gain |+| 3.3 V rail | 3.28 V / 1.0 A | LM317, R1 = 240 Ω, R2 = 390 Ω |+| Switching frequency | ~50 kHz, both stages | NE555 astable |+| Charge inductor | 220 µH, Isat ≥ 3 A, 2.26 A worst-case peak | ripple calc at Vin = 24 V |+| 5 V inductor | 100 µH, Isat ≥ 3 A, 2.33 A worst-case peak | ripple calc at Vin = 14.4 V |++Active devices: 2× LM324, 2× LM393, 2× NE555, 2× TL431, 1× LM317, 3× IRF4905 P-channel+MOSFETs, 3× 2N2222, SS36 and SS34 catch diodes, an SMBJ33A TVS, plus 42 resistors+(including the two 0.05 Ω 2512 shunts), 21 capacitors, two toroidal inductors, two+ferrite beads, a 6.3 A fuse and four terminal blocks. Full list in+[`hardware/bom.csv`](hardware/bom.csv).++## Generated, not drawn++Neither the schematic nor the PCB was drawn by hand in KiCad. Both are emitted by the+Python in [`kicad/generator-scripts/`](kicad/generator-scripts/):++| Script | Role |+|---|---|+| `build.py` | The design itself — every part placement and net, block by block |+| `schgen.py` | Schematic writer: grid snapping, wires, labels, ERC-clean tie-offs |+| `symlib.py` | Reads real `.kicad_sym` libraries and flattens `extends`/pin geometry |+| `pcbgen.py` | 4-layer board: placement, hand-routed switch loops, pours, autoroute hand-off |+| `fplib.py` | Reads real `.kicad_mod` footprints and emits placed instances |++The generators parse KiCad's own symbol and footprint libraries rather than inventing+geometry, which is why the output passes ERC and DRC against the stock libraries. Two+bugs worth recording were found and fixed this way: a rotation transform that placed+~150 pin labels at wrong coordinates when symbols were rotated, and a misreading of+KiCad's "unit 0" convention — unit 0 holds the pins *common to every unit* (an NE555's+shared GND/VCC), not an independently placeable unit, and treating it as a normal unit+left the timer's functional pins electrically orphaned. The fix went into the symbol+reader generally, not into a one-off patch for that part.++## Design record++The full engineering reasoning lives in [`docs/`](docs/), written as the design+progressed:++| Document | Contents |+|---|---|+| [`01-architecture-analysis.md`](docs/01-architecture-analysis.md) | **Superseded Rev 1.** A *battery-less* architecture using an MCU and an LM5175 buck-boost. Kept for the reasoning on MPPT-without-storage and brownout behaviour; it is **not** the board built here. |+| [`02-detailed-design-calculations.md`](docs/02-detailed-design-calculations.md) | Rev 2 — the design that was actually built. Every component value derived, block by block. |+| [`03-schematic-summary.md`](docs/03-schematic-summary.md) | Schematic completion notes, bugs found during generation, explicit design decisions. |+| [`04-pcb-layout-review.md`](docs/04-pcb-layout-review.md) | Layout review: loop geometry, Kelvin sensing, thermals, grounding, EMI risk. |++The design record is candid about its own limits, and that is worth preserving: it flags+the TO-220 loop-area floor, the absence of dedicated thermal vias as a deliberate+trade-off, and the fact that real EMI and loop-stability behaviour cannot be settled+without a built prototype.++## Verification notes++Published numbers were re-derived from the files, and five differences from the written+review are worth recording:++1. **Board size.** The layout review states 250 × 160 mm. The `Edge.Cuts` rectangle runs+   (4, 4) → (246, 156), so the board is **242 × 152 mm**; 250 × 160 is the KiCad *paper*+   size, not the board.+2. **Component count.** The schematic summary states "45 references, ~150 parts". The+   schematic actually carries **117 references**, and the PCB **121 footprints** — the+   difference being exactly the four mounting holes, which is self-consistent.+3. **In1.Cu is not a trace-free plane.** The review describes the stack as "In1.Cu solid+   GND plane" and concludes that "In1.Cu is the layer actually doing the return-path+   work," while calling out B.Cu as the compromised layer. The file says the reverse:+   **In1.Cu carries 198 routed segments, none of which are GND**, and its ground pour+   resolves into **9 islands** (largest 86.4 % of board area). **B.Cu carries only 25+   segments and pours as a single island covering 96 %.** The plane is still largely+   intact, so this is not a defect — but the return-path argument rests on In1.Cu+   specifically, and that argument should be re-checked against the layer's real+   geometry.+4. **Stackup thickness.** The defined stackup (0.2 core + 0.7 prepreg + 0.2 core + four+   0.035 copper + masks) sums to **1.26 mm**, while the board's overall thickness setting+   says 1.6 mm. Most fabs will simply build their standard 1.6 mm four-layer stack, so+   confirm which you want before ordering.+5. **"PCB layout has not been started."** True when the schematic summary was written;+   superseded by the layout review and by the routed board in this repo.++## Before you fabricate this++This board has **not** been built or bench-tested. Carried forward from the design record,+plus what the files show:++- **Confirm the real panel's Voc and its temperature coefficient.** Every input-referred+  rating (Q1/Q4 voltage, the SMBJ33A TVS, fuse, input caps) currently rests on an+  *assumed* 30 V worst case. Voc typically runs 20–30 % above Vmp and rises in cold.+- **Confirm the battery chemistry.** 14.2 V CV assumes SLA/AGM; a different chemistry+  moves that setpoint, and this is a fixed-setpoint charger with no temperature+  compensation.+- **Loop stability is a bench item.** The diode-OR combiner's behaviour as authority+  hands off between the three loops cannot be settled by hand calculation.+- **Resolve the stackup/thickness question** (note 4 above) with your fab.+- **Clear the 4 dangling tracks and tidy silkscreen** near the densest clusters — cosmetic,+  but worth a pass before ordering.+- **Thermals are calculated, not measured**: Q4 ≈ 1.4 W, Q1 ≈ 0.5 W. No dedicated thermal+  vias were added — a deliberate choice, since more copper at the switch node works+  against the EMI goal. If a prototype runs hot, the documented fix is a small copper+  island on an inner layer tied to the *source* pad (a quiet net), not the switch node.++## What's in this repo++```+docs/          the four design documents, plus the page images+kicad/         mppt-power-system.kicad_sch / .kicad_pcb / .kicad_pro+kicad/generator-scripts/   the Python that emits both files+hardware/      STEP + GLB 3D models, schematic PDF, BOM CSV+```++Get a local copy:++```bash+adom-wiki repo clone TylerK/mppt-power-system+```++Everything here is open — if you take the generator scripts somewhere more interesting,+open a pull request or start a discussion on this page.
page.jsonadded+34
@@ -0,0 +1,34 @@+{+  "slug": "mppt-power-system",+  "title": "Solar MPPT Charge System",+  "type": "component",+  "kind": "board",+  "version": "1.0.0",+  "description": "A 30 W solar battery charger with 5 V / 3.3 V rails built entirely from op-amps, comparators, timers and discrete FETs — no MCU and no dedicated power ICs. 4-layer KiCad board, schematic and PCB generated from Python. ERC 0, DRC 0 errors.",+  "tags": [+    "hardware",+    "board",+    "pcb",+    "kicad",+    "solar",+    "mppt",+    "power-electronics",+    "battery-charger",+    "buck-converter",+    "analog",+    "discrete",+    "lm324",+    "ne555",+    "tl431",+    "generated"+  ],+  "dependencies": {},+  "hero": {+    "type": "image",+    "path": "docs/hero.png"+  },+  "author": {+    "name": "TylerK",+    "email": "[email protected]"+  }+}\ No newline at end of file