Files
mangavalkandClaude Opus 5.5 dbc1f0041f Initial commit: plasma DMD driver project
- Design docs: 12 V DC power supply, safety and bring-up, ESP32 interface, BOM
- PSU schematic (schemdraw) and KiCad 10 carrier-board project with HV net classes
- ESP32 PlatformIO firmware skeleton with HV watchdog toggle
- Reference schematic of the Stern 520-5138-00 display PSU

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 02:31:50 +02:00

320 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Generate the PSU schematic (SVG + PNG) with schemdraw.
python -m pip install schemdraw matplotlib
python hardware/psu_schematic.py
"""
from pathlib import Path
import schemdraw
import schemdraw.elements as elm
OUT = Path(__file__).parent
schemdraw.use("matplotlib")
def gnd(d, at):
"""Ground symbol pointing down (for parts hanging below a node)."""
d.add(elm.Ground().theta(0).at(at))
def gnd_up(d, at):
"""Ground symbol pointing up (for parts standing on a negative rail)."""
d.add(elm.Ground().theta(180).at(at))
def vdd(d, at, name):
d.add(elm.Vdd().theta(0).at(at).label(name))
def ic(left, right, label, bottom=(), top=()):
pins = [elm.IcPin(name=n, side="left") for n in left]
pins += [elm.IcPin(name=n, side="right") for n in right]
pins += [elm.IcPin(name=n, side="bottom") for n in bottom]
pins += [elm.IcPin(name=n, side="top") for n in top]
return elm.Ic(pins=pins, pinspacing=1.2, edgepadH=0.6, edgepadW=1.2).right().label(label, "top")
def title(d, at, text):
d.add(elm.Label().at(at).label(text, fontsize=12, halign="left", valign="bottom"))
def note(d, at, text, size=9, color="black"):
d.add(elm.Label().at(at).label(text, fontsize=size, halign="left", valign="top", color=color))
# For vertical parts schemdraw's "left"/"right" label locations are the part's ends;
# these give the visual side instead. A .reverse()d part swaps its sides again.
R_DOWN, L_DOWN, R_UP, L_UP = "bottom", "top", "bottom", "top"
def shunt_down(d, at, part, label):
"""part is a factory: elements auto-add when created inside a Drawing."""
d.add(elm.Dot().at(at))
d.add(part().at(at).down().label(label, R_DOWN))
gnd(d, d.here)
def shunt_up(d, at, part, label):
d.add(elm.Dot().at(at))
d.add(part().at(at).up().label(label, R_UP))
gnd_up(d, d.here)
# ---------------------------------------------------------------------------
def block_input(d, ox, oy):
title(d, (ox - 3, oy + 3.5), "1. 12 V input and +5 V")
d.add(elm.Dot(open=True).at((ox, oy)).label("J1 +12V in\n(certified brick)", "left"))
d.add(elm.Fuse().right().label("F1 3A T"))
n1 = d.here
d.add(elm.Line().right().length(3))
n2 = d.here
d.add(elm.Line().right().length(3))
n3 = d.here
d.add(elm.Line().right().length(2))
d.add(elm.Dot())
d.add(elm.DiodeTVS().at(n1).down().reverse().label("D1\nP6KE15A", L_DOWN))
gnd(d, d.here)
d.add(elm.Dot().at(n1))
shunt_down(d, n2, lambda: elm.Capacitor(polar=True), "C1\n470µF 25V")
d.add(elm.Dot().at(n3))
d.add(elm.Line().at(n3).up().length(1.2))
vdd(d, d.here, "+12V")
d.add(elm.Dot(open=True).at((ox, oy - 2.5)).label("J1 GND", "left"))
gnd(d, (ox, oy - 2.5))
u1 = d.add(ic(["VIN"], ["VOUT"], "U1 Mini560\n5V fixed buck", bottom=["GND"])
.at((n3.x + 2, oy)).anchor("VIN"))
gnd(d, u1.GND)
d.add(elm.Line().at(u1.VOUT).right().length(2))
n4 = d.here
d.add(elm.Line().right().length(2))
vdd(d, d.here, "+5V")
shunt_down(d, n4, lambda: elm.Capacitor(polar=True), "C2\n100µF 10V")
def block_enable(d, ox, oy):
title(d, (ox - 3, oy + 3.5), "2. HV enable relay + hardware watchdog")
# charge pump
d.add(elm.Dot(open=True).at((ox, oy)).label("ESP32 GPIO21\n(toggles every\nscanned row)", "left"))
d.add(elm.Capacitor().right().label("C3 100nF"))
a = d.here
d.add(elm.Diode().right().label("D2 1N4148"))
b = d.here
d.add(elm.Line().right().length(2.5))
c = d.here
d.add(elm.Line().right().length(2.5))
q1 = d.add(elm.NFet().theta(0).reverse().anchor("gate").label("Q1\n2N7000", "right"))
d.add(elm.Dot().at(a))
d.add(elm.Diode().at(a).down().reverse().label("D3\n1N4148", L_DOWN))
gnd(d, d.here)
shunt_down(d, b, lambda: elm.Capacitor(polar=True), "C4\n10µF")
shunt_down(d, c, lambda: elm.Resistor(), "R1\n100k")
gnd(d, q1.source)
# relay module
k1 = d.add(ic(["IN", "VCC"], ["NO", "COM"], "K1 relay module\n5V, opto, active-low IN",
bottom=["GND"]).at((q1.drain.x + 5, q1.drain.y + 2.5)).anchor("IN"))
d.add(elm.Wire("|-").at(q1.drain).to(k1.IN))
d.add(elm.Line().at(k1.VCC).left().length(1.5))
vdd(d, d.here, "+5V")
gnd(d, k1.GND)
d.add(elm.Line().at(k1.COM).right().length(2))
vdd(d, d.here, "+12V")
d.add(elm.Line().at(k1.NO).right().length(3).label("+12V_HV", "right"))
note(d, (ox - 6, oy - 4.5),
"Static GPIO (high, low, reset, crash) → no charge on C4 → Q1 off → relay open → HV off.\n"
"Drop-out ≈ 1 s (C4 × R1). Check your relay module's IN polarity.")
def block_pos(d, ox, oy):
title(d, (ox - 3, oy + 4.5), "3. Positive HV: boost module + original regulator → +65 V")
d.add(elm.Dot(open=True).at((ox, oy)).label("+12V_HV", "left"))
d.add(elm.Fuse().right().label("F2 1A T"))
d.add(elm.Line().right().length(1))
u2 = d.add(ic(["VIN"], ["HV+"], "U2 NCH6100HV\nset 90V", bottom=["GND"]).anchor("VIN"))
gnd(d, u2.GND)
y0 = oy
d.add(elm.Line().at(u2.absanchors["HV+"]).right().length(1))
d.add(elm.Fuse().right().label("F4 100mA F"))
x = d.here.x
d.add(elm.Label().at((x + 0.3, y0 + 0.2)).label("+90V raw", "top", fontsize=9, halign="left"))
# raw rail: bulk cap, bleeder, HV-present LED
shunt_down(d, (x + 1.5, y0), lambda: elm.Capacitor(polar=True), "C5\n22µF\n250V")
shunt_down(d, (x + 4.5, y0), lambda: elm.Resistor(), "R2\n330k")
d.add(elm.Dot().at((x + 7.5, y0)))
d.add(elm.Resistor().at((x + 7.5, y0)).down().label("R3\n100k 1W", R_DOWN))
d.add(elm.LED().down().label("LED1", R_DOWN))
gnd(d, d.here)
# regulator (original Q1/Q3 section)
xa, xb, xd = x + 12, x + 16, x + 21
d.add(elm.Resistor().at((xb, y0)).down().label("R4\n1.5k", L_DOWN))
qa = d.add(elm.BjtPnp(circle=True).theta(0).anchor("emitter").label("Q2\nMPSA92", "right"))
by = qa.base.y
d.add(elm.Dot().at((xa, y0)))
d.add(elm.Zener().endpoints((xa, y0), (xa, by)).reverse().label("D4\n3.9V\n1N5228", R_DOWN))
d.add(elm.Dot().at((xa, by)))
d.add(elm.Line().at((xa, by)).to(qa.base))
d.add(elm.Resistor().at((xa, by)).down().label("R5\n47k 1W", L_DOWN))
gnd(d, d.here)
qby = qa.collector.y - 1.2
d.add(elm.Line().at(qa.collector).toy(qby))
nqb = d.add(elm.Dot()).center
d.add(elm.Zener().at(nqb).down().reverse().label("D5\n68V\n1N4760A", R_DOWN))
gnd(d, d.here)
q3 = d.add(elm.BjtNpn(circle=True).theta(0).anchor("base").at((xd, qby))
.label("Q3\nMJE15030\n(heatsink)", "right"))
d.add(elm.Line().at(nqb).to(q3.base))
d.add(elm.Line().at(q3.collector).toy(y0))
d.add(elm.Line().at((x, y0)).tox(q3.collector.x))
oy2 = q3.emitter.y - 0.8
d.add(elm.Line().at(q3.emitter).toy(oy2))
d.add(elm.Line().right().length(2.5))
o1 = d.here
d.add(elm.Line().right().length(3))
o2 = d.here
d.add(elm.Line().right().length(3))
d.add(elm.Dot(open=True).label("+65V to panel", "right"))
shunt_down(d, o1, lambda: elm.Capacitor(), "C6\n0.1µF\n500V")
shunt_down(d, o2, lambda: elm.Resistor(), "R6\n330k")
def block_neg(d, ox, oy):
title(d, (ox - 3, oy + 4.5),
"4. Negative HV: isolated converter + floating boost module + original regulator → −112 V / −100 V")
d.add(elm.Dot(open=True).at((ox, oy)).label("+12V_HV", "left"))
d.add(elm.Fuse().right().label("F3 1A T"))
d.add(elm.Line().right().length(1))
u3 = d.add(ic(["VIN", "GND"], ["−VO", "+VO"], "U3 isolated DC-DC\n12V→12V 6–10W").anchor("VIN"))
d.add(elm.Line().at(u3.GND).left().length(1))
gnd(d, d.here)
d.add(elm.Line().at(u3.absanchors["+VO"]).right().length(2.5))
u4 = d.add(ic(["VIN"], ["HV+"], "U4 NCH6100HV\nset 130V", bottom=["GND"]).anchor("VIN"))
jy = u4.GND.y - 1.5
d.add(elm.Line().at(u3.absanchors["−VO"]).right().length(1.2))
d.add(elm.Line().toy(jy))
d.add(elm.Line().tox(u4.GND.x))
d.add(elm.Dot())
hv = u4.absanchors["HV+"]
d.add(elm.Line().at(hv).right().length(1.5))
gnd(d, d.here)
yb = u4.GND.y - 6 # −130 V raw rail
d.add(elm.Line().at(u4.GND).toy(yb))
d.add(elm.Fuse().right().label("F5 100mA F", "bottom"))
x = d.here.x
d.add(elm.Label().at((x + 0.3, yb - 0.3)).label("−130V raw", "bottom", fontsize=9, halign="left"))
d.add(elm.EncircleBox([u4], padx=1.0, pady=1.2).linestyle("--").color("red"))
note(d, (u3.center.x - 3, yb - 0.8),
"⚠ U3's output side, U4 and this whole\nnet sit at −130 V: insulate, never touch.", 8, "red")
shunt_up(d, (x + 1.5, yb), lambda: elm.Capacitor(polar=True).reverse(), "C7\n22µF\n250V")
shunt_up(d, (x + 4.5, yb), lambda: elm.Resistor(), "R7\n330k")
d.add(elm.Dot().at((x + 7.5, yb)))
d.add(elm.Resistor().at((x + 7.5, yb)).up().label("R8\n100k 1W", R_UP))
d.add(elm.LED().up().reverse().label("LED2", L_UP))
gnd_up(d, d.here)
# regulator (original Q2/Q4/Q5 section)
xa, xb, xz, xd = x + 12, x + 16, x + 19, x + 23
d.add(elm.Resistor().at((xb, yb)).up().label("R9\n1.5k", L_UP))
qa = d.add(elm.BjtNpn(circle=True).theta(0).anchor("emitter").label("Q4\nMPSA42", "right"))
by = qa.base.y
d.add(elm.Dot().at((xa, yb)))
d.add(elm.Zener().endpoints((xa, yb), (xa, by)).label("D6\n3.9V\n1N5228", L_UP))
d.add(elm.Dot().at((xa, by)))
d.add(elm.Line().at((xa, by)).to(qa.base))
d.add(elm.Resistor().at((xa, by)).up().label("R10\n47k 1W", L_UP))
gnd_up(d, d.here)
qby = qa.collector.y + 0.6
d.add(elm.Line().at(qa.collector).toy(qby))
d.add(elm.Line().tox(xz))
nqb = d.add(elm.Dot()).center
d.add(elm.Zener().at(nqb).up().label("D7\n13V\n1N4743", R_UP))
m = d.add(elm.Dot()).center
d.add(elm.Zener().up().label("D8\n100V\n1N4764A", R_UP))
gnd_up(d, d.here)
q4 = d.add(elm.BjtPnp(circle=True).theta(0).anchor("base").at((xd, qby))
.label("Q5\nMJE15031\n(heatsink)", "right"))
d.add(elm.Line().at(nqb).to(q4.base))
d.add(elm.Line().at(q4.collector).toy(yb))
d.add(elm.Line().at((x, yb)).tox(q4.collector.x))
# −112 V output
out_x = xd + 17
y112 = q4.emitter.y + 0.6
d.add(elm.Line().at(q4.emitter).toy(y112))
d.add(elm.Line().right().length(6))
o1 = d.here
d.add(elm.Line().right().length(3))
o2 = d.here
d.add(elm.Line().tox(out_x))
d.add(elm.Dot(open=True).label("−112V to panel", "right"))
shunt_up(d, o1, lambda: elm.Capacitor(), "C8\n0.1µF\n500V")
shunt_up(d, o2, lambda: elm.Resistor(), "R11\n330k")
# −100 V: Q5 follower driven from node M (D6/D7 junction)
yq5 = m.y + 5.5
d.add(elm.Line().at(m).tox(m.x + 1.5))
d.add(elm.Line().toy(yq5))
d.add(elm.Resistor().right().tox(xd + 4.5).label("R12 1.5k", "top"))
q5 = d.add(elm.BjtNpn(circle=True).theta(0).anchor("base").label("Q6\nMJE15030\n(heatsink)", "right"))
d.add(elm.Resistor().at(q5.collector).up().label("R13\n2k 5W", R_UP))
gnd_up(d, d.here)
y100 = q5.emitter.y - 0.6
d.add(elm.Line().at(q5.emitter).toy(y100))
d.add(elm.Line().right().length(6))
o3 = d.here
d.add(elm.Line().tox(out_x))
d.add(elm.Dot(open=True).label("−100V to panel", "right"))
shunt_up(d, o3, lambda: elm.Capacitor(), "C9\n0.1µF\n500V")
def block_out(d, ox, oy):
title(d, (ox - 4, oy + 3.5), "5. Panel power connector")
names = ["+5V", "+12V", "+65V", "GND", "−100V", "−112V"]
j2 = d.add(ic(list(reversed(names)), [], "J2 to panel\n(keyed, ≥300V rated)").at((ox, oy)).anchor("+5V"))
for n in names:
if n == "+12V":
d.add(elm.Switch().at(j2.absanchors[n]).left().length(3).label("JP1", "top"))
d.add(elm.Label().at(d.here).label(n, "left"))
else:
d.add(elm.Line().at(j2.absanchors[n]).left().length(3).label(n, "left"))
note(d, (ox - 4, j2.absanchors["−112V"].y - 1.5),
"Pin order is a placeholder: map it to YOUR panel's\nconnector before wiring. Fit JP1 only if the\npanel uses +12V.")
def block_notes(d, ox, oy):
title(d, (ox, oy), "Notes")
note(d, (ox, oy - 0.4),
"• Panel voltages are set by zeners D5 (68V → about +65V out) and D7+D8 (113V → about −112V / −100V out), copied from Stern 520-5138-00.\n"
" Module trim pots only set the raw rails, so a mis-set module can't over-volt the panel.\n"
"• Original board refs → this design: Q1→Q2, Q2→Q4, Q3→Q3, Q4→Q5, Q5→Q6, D3→D4, D4→D6, D5→D5, D6→D8, D7→D7, R2→R5, R3→R10, R4→R4, R5→R9,\n"
" R6→R6, R7→R11, R8→R13, R10→R12, C3→C6, C4→C8, C7→C9. Designators match the KiCad project.\n"
"• Set U2/U4 with no load, then lock the trim pots. First power-up with the panel: 1 kΩ in series with each HV output, measure current.\n"
"• Q3/Q4/Q5 on small heatsinks. HV transistors and zeners from a real distributor. Raw caps ≥ 250 V, output caps 500 V, ≥ 3 mm HV clearance.\n"
"• Bleeders discharge all rails at power-off; LEDs are a hint only. Always measure before touching.",
size=9)
with schemdraw.Drawing(show=False) as d:
d.config(fontsize=9, unit=2.5)
title(d, (-8, 8), "Plasma DMD — 12 V DC input power supply (rev 0.1, unverified)")
block_input(d, 0, 0)
block_enable(d, 42, 0)
block_pos(d, 0, -17)
block_out(d, 58, -14)
block_neg(d, 0, -36)
block_notes(d, -8, -56)
d.save(str(OUT / "psu-schematic.svg"))
d.save(str(OUT / "psu-schematic.png"), dpi=150)
print("saved")