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>
This commit is contained in:
commit
dbc1f0041f
21 files changed
+13853
No files matched your search
+19
@@ -0,0 +1,19 @@
|
||||
# PlatformIO
|
||||
firmware/.pio/
|
||||
firmware/.vscode/
|
||||
|
||||
# KiCad backups, caches and local state
|
||||
*-backups/
|
||||
*.kicad_prl
|
||||
fp-info-cache
|
||||
*.lck
|
||||
\#auto_saved_files#
|
||||
_autosave-*
|
||||
*.bak
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
|
||||
# KiCad generated reports
|
||||
hardware/kicad/erc.rpt
|
||||
*.net
|
||||
@@ -0,0 +1,41 @@
|
||||
# Plasma DMD
|
||||
|
||||
Drive a second-hand 128×32 pinball **plasma** dot-matrix display (originally on a Williams WPC
|
||||
DMD controller) standalone, from a **Wemos D1 mini ESP32**, powered from a single **12 V DC** brick.
|
||||
|
||||
The display power supply is built from cheap modules plus a copy of the regulator section of the
|
||||
Stern/Sega display PSU (520-5138-00). The original transformer and AC front end are not used.
|
||||
|
||||
> ⚠️ **High voltage.** The panel runs on about +65 V, −100 V and −112 V, which is about 175 V
|
||||
> between rails. That can injure you. Read [docs/safety.md](docs/safety.md) before powering anything.
|
||||
|
||||
## The panel
|
||||
|
||||
- Bought second-hand on 18 September 2021 for €37.
|
||||
- Originally driven by a Williams WPC DMD controller board. No original controller or PSU came with it.
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| [docs/power-supply.md](docs/power-supply.md) | PSU design: block diagram, analysis of the original board, changes |
|
||||
| [hardware/psu-schematic.svg](hardware/psu-schematic.svg) | PSU schematic (generated by [hardware/psu_schematic.py](hardware/psu_schematic.py)) |
|
||||
| [hardware/kicad/](hardware/kicad/) | KiCad 10 project for the PSU carrier board (schematic + net classes, PCB not started) |
|
||||
| [docs/safety.md](docs/safety.md) | Safety design (HV watchdog, fusing, layout, bring-up procedure) |
|
||||
| [docs/esp32-interface.md](docs/esp32-interface.md) | ESP32 pin plan, 74HCT245 level shifter, panel signals |
|
||||
| [docs/bom.md](docs/bom.md) | Bill of materials |
|
||||
| [reference/](reference/) | Original Stern 520-5138-00 display PSU schematic |
|
||||
| [firmware/](firmware/) | PlatformIO project for the ESP32 |
|
||||
|
||||
## Status
|
||||
|
||||
- [ ] Identify panel: manufacturer/part number from its label
|
||||
- [ ] Map the panel's power connector pins (WPC panel ≠ Stern CN2 pinout) and check if +12 V is used
|
||||
- [ ] Map the panel's 14-pin data connector
|
||||
- [ ] Lay out the PSU PCB in KiCad ([hardware/kicad/](hardware/kicad/README.md))
|
||||
- [ ] Order parts ([docs/bom.md](docs/bom.md))
|
||||
- [ ] Build and bench-test the PSU **without the panel** ([bring-up](docs/safety.md#bring-up-procedure))
|
||||
- [ ] Build HV watchdog and verify that the relay drops when toggling stops
|
||||
- [ ] Firmware: row scanning with test pattern
|
||||
- [ ] First power-up with panel (series resistors, current measurement)
|
||||
- [ ] Firmware: greyscale, content source (Wi-Fi / serial)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Bill of materials
|
||||
|
||||
Designators match the KiCad project ([../hardware/kicad/](../hardware/kicad/README.md)).
|
||||
Footprint choices are in [gen_kicad.py](../hardware/kicad/gen_kicad.py).
|
||||
|
||||
Prices are rough estimates.
|
||||
|
||||
## Modules (AliExpress unless noted)
|
||||
|
||||
| Qty | Part | Notes | About |
|
||||
|---|---|---|---|
|
||||
| 1 | 12 V 3 A power brick | **Certified brand, from a local retailer** (e.g. Mean Well GST36A12) | €15 |
|
||||
| 1 | Mini560 5 V fixed buck module | Fixed output, no trim pot | €2 |
|
||||
| 2 | NCH6100HV nixie HV boost module | Input 9–16 V, output 85–235 V adjustable | €16 |
|
||||
| 1 | Isolated DC-DC 12 V → 12 V, 6–10 W | e.g. Mornsun URB1212YMD-6WR3 / -10WR3 | €6 |
|
||||
| 1 | 5 V 1-channel relay module, with optocoupler | Switches the 12 V feed to the HV modules | €2 |
|
||||
| 1 | Wemos D1 mini ESP32 | | €5 |
|
||||
|
||||
## Regulator section (from Mouser / Farnell / Reichelt)
|
||||
|
||||
| Qty | Part | Ref (KiCad) | Ref (original Stern board) |
|
||||
|---|---|---|---|
|
||||
| 2 | MJE15030 (NPN, TO-220) | Q3, Q6 | Q3, Q5 |
|
||||
| 1 | MJE15031 (PNP, TO-220) | Q5 | Q4 |
|
||||
| 1 | MPSA92 (PNP, TO-92) | Q2 | Q1 |
|
||||
| 1 | MPSA42 (NPN, TO-92) | Q4 | Q2 |
|
||||
| 2 | 1N5228 (3.9 V zener) | D4, D6 | D3, D4 |
|
||||
| 1 | 1N4760A (68 V zener) | D5 | D5 |
|
||||
| 1 | 1N4764A (100 V zener) | D8 | D6 |
|
||||
| 1 | 1N4743 (13 V zener) | D7 | D7 |
|
||||
| 2 | 47 kΩ 1 W | R5, R10 | R2, R3 |
|
||||
| 3 | 1.5 kΩ ½ W | R4, R9, R12 | R4, R5, R10 |
|
||||
| 2 | 330 kΩ ½ W | R6, R11 | R6, R7 |
|
||||
| 1 | 2 kΩ 5 W | R13 | R8 |
|
||||
| 3 | 0.1 µF 500 V film | C6, C8, C9 | C3, C4, C7 |
|
||||
| 3 | TO-220 heatsink | for Q3, Q5, Q6 | |
|
||||
|
||||
## Additions
|
||||
|
||||
| Qty | Part | Purpose |
|
||||
|---|---|---|
|
||||
| 1 | P6KE15A TVS diode (DO-15) | 12 V input surge protection (D1) |
|
||||
| 1 | 470 µF 25 V electrolytic | 12 V input bulk (C1) |
|
||||
| 1 | 100 µF 10 V electrolytic | +5 V bulk (C2) |
|
||||
| 2 | 22 µF 250 V electrolytic (12.5 mm) | Raw HV rail buffer (C5, C7) |
|
||||
| 2 | 330 kΩ ½ W | Raw HV rail bleeders (R2, R7) |
|
||||
| 2 | LED + 100 kΩ 1 W | HV-present indicators (LED1/R3, LED2/R8) |
|
||||
| 1 | 74HCT245 + DIP socket | 3.3 V → 5 V level shifter |
|
||||
| 1 | 2N7000, 2 × 1N4148, 100 nF, 10 µF, 100 kΩ | HV watchdog charge pump (Q1, D2, D3, C3, C4, R1) |
|
||||
| 2 | 1 kΩ 1 W | Series resistors for first power-up |
|
||||
| 5 | 5×20 mm PCB fuse holders + fuses: 3 A T (F1), 2 × 1 A T (F2, F3), 2 × 100 mA F (F4, F5) | |
|
||||
| — | Phoenix MKDS 5.08 mm screw terminals: 3 × 2-pin (J1, J6, J7), 2 × 3-pin (J5, J8), 1 × 4-pin (J3), 1 × 5-pin (J4) | Module connections |
|
||||
| 1 | Molex KK-396 8-pin header (J2) + mating housing | Panel power |
|
||||
| 1 | 2-pin header + jumper (JP1) | +12 V to panel |
|
||||
| — | M3 standoffs (H1–H4), 300 V-rated wire, plastic enclosure | |
|
||||
@@ -0,0 +1,35 @@
|
||||
# ESP32 interface
|
||||
|
||||
Board: **Wemos D1 mini ESP32**, powered from the 5 V rail.
|
||||
|
||||
## Level shifting
|
||||
|
||||
The panel inputs are 5 V TTL and the ESP32 is 3.3 V. Use a **74HCT245** (HCT only, because it accepts 3.3 V
|
||||
inputs) powered from 5 V, with DIR tied high (A→B) and OE tied low.
|
||||
|
||||
## Pin plan
|
||||
|
||||
| Signal (to panel, through 74HCT245) | GPIO | Notes |
|
||||
|---|---|---|
|
||||
| Dot clock | 18 | VSPI SCK |
|
||||
| Serial dot data | 23 | VSPI MOSI |
|
||||
| Column latch | 17 | |
|
||||
| Row data | 16 | |
|
||||
| Row clock | 26 | |
|
||||
| Display enable | 22 | Pull to *off* state at the 245 input |
|
||||
| HV watchdog toggle | 21 | Charge pump → relay (see safety.md) |
|
||||
| +65 V monitor (optional) | 34 | ADC, 2 × 1 MΩ + 33 kΩ divider |
|
||||
|
||||
The strapping pins (0, 2, 5, 12, 15) are avoided so nothing twitches during boot.
|
||||
|
||||
## Panel data connector
|
||||
|
||||
- [ ] Map the panel's 14-pin data connector (signal ↔ pin) for this specific panel before wiring.
|
||||
|
||||
## Scanning
|
||||
|
||||
- 128 columns × 32 rows, about 120 Hz frame rate (as on WPC).
|
||||
- Per row: shift 128 bits of dot data (SPI), latch the columns, advance the row (row data is high
|
||||
for the first row only, then clock the row once per row).
|
||||
- The panel must never sit with one row lit. Scanning runs continuously, and the HV watchdog is
|
||||
toggled once per scanned row (about 2 kHz, so the charge pump stays charged).
|
||||
@@ -0,0 +1,82 @@
|
||||
# Power supply design
|
||||
|
||||
## Goal
|
||||
|
||||
Generate every panel rail from one **12 V DC** input, using cheap modules. Panel voltages are set by
|
||||
**zener references** (copied from the original Stern board), not by module trim pots. A
|
||||
mis-set or failed module can't push the panel above its rated voltage.
|
||||
|
||||
## The original board (Stern/Sega 520-5138-00)
|
||||
|
||||
See [../reference/stern-520-5138-00-display-psu-schematic.jpg](../reference/stern-520-5138-00-display-psu-schematic.jpg).
|
||||
|
||||
| CN2 pin | Label | How it is produced | Real value |
|
||||
|---|---|---|---|
|
||||
| 8 | +60 V | 88 VAC → D1 → C1 (about +120 V raw) → Q3 (MJE15030) emitter follower, base on D5 68 V zener | about +62–67 V |
|
||||
| 1 | −120 V | 110 VAC → D2 → C2 (about −150 V raw) → Q4 (MJE15031) follower, base on D6 100 V + D7 13 V zeners | about −112 V |
|
||||
| 2 | −100 V | Q5 (MJE15030) follower from the D6/D7 junction. Collector to GND via R8 2 kΩ 5 W. | about −100 V |
|
||||
| 7 | +12 V | Passed through from CN1 | |
|
||||
| 6 | +5 V | Passed through from CN1 | |
|
||||
| 4, 5 | GND | | |
|
||||
|
||||
- Q1/D3/R2/R4 and Q2/D4/R3/R5: constant-current feeds (about 2 mA) for the zener references.
|
||||
- R6/R7 (330 kΩ): bleeder resistors. C3/C4/C7: 0.1 µF 500 V decoupling.
|
||||
|
||||
## New design
|
||||
|
||||
Full schematic: [../hardware/psu-schematic.svg](../hardware/psu-schematic.svg) (PNG alongside).
|
||||
It is generated from [../hardware/psu_schematic.py](../hardware/psu_schematic.py):
|
||||
|
||||
```bash
|
||||
python -m pip install schemdraw matplotlib
|
||||
python hardware/psu_schematic.py
|
||||
```
|
||||
|
||||
The schematic's reference designators are new; the notes on the sheet map them to the original board.
|
||||
|
||||
Replace the transformer and AC front end (D1, D2, C1, C2, F1, R1) with DC boost modules. Keep the
|
||||
regulator section as it is.
|
||||
|
||||
```
|
||||
Mains ─► [Certified 12V 3A brick] ─► F 3A ─┬──────────────► +12V (only if the panel uses it)
|
||||
│
|
||||
├─► [Mini560 5V buck] ─► +5V ─► panel logic, ESP32, 74HCT245
|
||||
│
|
||||
└─► [Relay, NO contact] ◄── HV watchdog (see safety.md)
|
||||
│
|
||||
┌──────────────┴──────────────┐
|
||||
▼ F 1A slow ▼ F 1A slow
|
||||
[NCH6100HV #1 set 90V] [Isolated 12→12V DC-DC, 6–10W]
|
||||
│ │
|
||||
│ [NCH6100HV #2 set 130V]
|
||||
│ out+ ─► GND
|
||||
▼ F 100mA, +90V raw ▼ F 100mA, −130V raw
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Copy of original regulator section: │
|
||||
│ Q1/Q3/D3/D5/R2/R4 → +62–67V │
|
||||
│ Q2/Q4/D4/D6/D7/R3/R5 → −112V │
|
||||
│ Q5/R8/R10 → −100V │
|
||||
│ R6/R7 bleeders, C3/C4/C7 500V caps │
|
||||
└───────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Why the isolated converter
|
||||
|
||||
A boost module's output negative is shared with its input ground. Powering module #2 through an
|
||||
isolated DC-DC lets its output float, so its **+ output is tied to GND** and its − output becomes
|
||||
−130 V. A second certified 12 V wall adapter would also work.
|
||||
|
||||
### Changes compared with the original
|
||||
|
||||
- **Removed:** D1, D2, C1, C2, F1, R1 (the boost modules replace the AC front end; the module current
|
||||
limit plus a 100 mA fast fuse replaces R1).
|
||||
- **Added:** 10–22 µF / 250 V cap and a 330 kΩ bleeder across each raw HV rail.
|
||||
- **Added:** HV-present LED (LED + 100 kΩ 1 W) on each raw HV rail.
|
||||
- **Headroom:** +90 V raw feeding the 68 V-referenced stage, and −130 V raw feeding the
|
||||
113 V-referenced stage, give the followers enough headroom with modest dissipation. Q3/Q4/Q5 get small heatsinks.
|
||||
|
||||
## Open questions
|
||||
|
||||
- [ ] Real HV current draw of the panel, so you can size the isolated DC-DC (6 W vs 10 W)
|
||||
- [ ] Panel power connector pinout and whether +12 V is used
|
||||
- [ ] Check that NCH6100HV has a usable enable/shutdown pin (otherwise, switch with the relay only, as drawn)
|
||||
@@ -0,0 +1,57 @@
|
||||
# Safety
|
||||
|
||||
About 175 V sits between the +65 V and −112 V rails. Treat every HV node as live until you have
|
||||
measured it.
|
||||
|
||||
## Design measures
|
||||
|
||||
1. **Only certified mains equipment.** The 12 V brick is the only mains-connected part. Buy a
|
||||
certified brand (e.g. Mean Well GST36A12) from a normal retailer, not AliExpress.
|
||||
2. **Hardware HV watchdog.** The relay on the HV modules' 12 V feed is driven through a charge pump:
|
||||
|
||||
```
|
||||
GPIO21 ──┤├──── A ──►|── B ───┬───────┬──── 2N7000 gate 2N7000 drain ─► relay IN (active-low)
|
||||
100nF │ 1N4148 │ │ 2N7000 source ─► GND
|
||||
┴ 1N4148 ═ 10µF ▯ 100k
|
||||
▲ (anode GND) │ │
|
||||
GND GND GND
|
||||
```
|
||||
|
||||
Node A gets a diode to GND (cathode at A), and a diode from A to B (anode at A). This rectifies
|
||||
the toggling signal into a DC voltage on B. A constant high or low level gives no charge.
|
||||
|
||||
The firmware toggles GPIO21 once per scanned row (about 2 kHz). If it crashes, or the pin sticks high or low, or
|
||||
the ESP32 resets, the cap discharges and the relay drops within about 1 s. A stuck row can't burn the panel.
|
||||
(Check the polarity of your relay module's input. Some are active-low.)
|
||||
3. **Fail-safe defaults.** The relay is normally open. Put a 10 kΩ pull-down on the watchdog
|
||||
path. Pull the panel ENABLE line to its *off* state at the 74HCT245 input, so a booting ESP32
|
||||
can't light anything.
|
||||
4. **Sequencing.** 5 V comes up, then the ESP32 boots and starts scanning, and only then does the watchdog close the relay.
|
||||
5. **Fusing.**
|
||||
|
||||
| Where | Fuse |
|
||||
|---|---|
|
||||
| 12 V input | 3 A |
|
||||
| Each HV module input | 1 A slow-blow |
|
||||
| Each raw HV rail | 100 mA fast |
|
||||
|
||||
6. **Discharge.** 330 kΩ bleeders on raw and regulated rails. HV-present LEDs are a hint only;
|
||||
always measure before you touch anything.
|
||||
7. **Layout.**
|
||||
- Put all HV parts on one side, with ≥ 3 mm clearance between HV tracks.
|
||||
- Use wire and caps rated for ≥ 300 V/500 V.
|
||||
- Use a keyed connector to the panel.
|
||||
- Close it in a plastic enclosure with no exposed HV.
|
||||
- Tie all grounds at one point.
|
||||
8. **Semiconductors from real distributors** (Mouser, Farnell, Reichelt). Fake HV transistors are
|
||||
common on AliExpress.
|
||||
|
||||
## Bring-up procedure
|
||||
|
||||
1. Build the 5 V and 12 V side only. Check the voltages.
|
||||
2. Connect one HV module at a time, with **no load**. Set NCH6100HV #1 to 90 V and #2 to 130 V. Lock the pots with nail varnish.
|
||||
3. Connect the regulator section, still without the panel. Check for about +65 V, −112 V and −100 V and that the bleeders discharge them.
|
||||
4. Test the watchdog: stop toggling GPIO21, and the HV must drop within about 1 s.
|
||||
5. Map the panel's power connector against the measured rails. Get a second check (photo/label).
|
||||
6. First power-up with the panel: 1 kΩ in series with each HV rail and a running test pattern.
|
||||
Measure the current per rail, then remove the series resistors.
|
||||
@@ -0,0 +1,5 @@
|
||||
[env:wemos_d1_mini32]
|
||||
platform = espressif32
|
||||
board = wemos_d1_mini32
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
@@ -0,0 +1,77 @@
|
||||
// Plasma DMD driver skeleton for Wemos D1 mini ESP32.
|
||||
// Scans a 128x32 plasma panel continuously and toggles the HV watchdog once per row.
|
||||
// Signal polarities (enable, latch, row data) are NOT yet verified against the panel.
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
|
||||
// Pins, see docs/esp32-interface.md
|
||||
constexpr int PIN_DOT_CLK = 18; // VSPI SCK
|
||||
constexpr int PIN_DOT_DATA = 23; // VSPI MOSI
|
||||
constexpr int PIN_COL_LATCH = 17;
|
||||
constexpr int PIN_ROW_DATA = 16;
|
||||
constexpr int PIN_ROW_CLK = 26;
|
||||
constexpr int PIN_ENABLE = 22;
|
||||
constexpr int PIN_HV_WDOG = 21; // charge pump -> HV relay
|
||||
|
||||
constexpr int WIDTH = 128;
|
||||
constexpr int HEIGHT = 32;
|
||||
constexpr int ROW_BYTES = WIDTH / 8;
|
||||
|
||||
// TODO: verify polarities on the real panel
|
||||
constexpr int ENABLE_ON = HIGH;
|
||||
constexpr int ENABLE_OFF = !ENABLE_ON;
|
||||
|
||||
uint8_t frame[HEIGHT][ROW_BYTES];
|
||||
|
||||
static void makeTestPattern() {
|
||||
for (int y = 0; y < HEIGHT; y++)
|
||||
for (int b = 0; b < ROW_BYTES; b++)
|
||||
frame[y][b] = ((y / 4 + b) & 1) ? 0xFF : 0x00; // checkerboard
|
||||
}
|
||||
|
||||
static void pulse(int pin) {
|
||||
digitalWrite(pin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
digitalWrite(pin, LOW);
|
||||
}
|
||||
|
||||
static void scanFrame() {
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
SPI.writeBytes(frame[y], ROW_BYTES);
|
||||
|
||||
digitalWrite(PIN_ENABLE, ENABLE_OFF); // blank while switching rows
|
||||
pulse(PIN_COL_LATCH);
|
||||
digitalWrite(PIN_ROW_DATA, y == 0 ? HIGH : LOW);
|
||||
pulse(PIN_ROW_CLK);
|
||||
digitalWrite(PIN_ENABLE, ENABLE_ON);
|
||||
|
||||
delayMicroseconds(200); // row on-time, ~120 Hz frame rate incl. overhead
|
||||
|
||||
// Toggle per row (~2 kHz) to keep the charge pump charged. If scanning stops, HV drops.
|
||||
digitalWrite(PIN_HV_WDOG, !digitalRead(PIN_HV_WDOG));
|
||||
}
|
||||
digitalWrite(PIN_ENABLE, ENABLE_OFF);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// Safe state first: display disabled, watchdog idle (HV stays off).
|
||||
pinMode(PIN_ENABLE, OUTPUT);
|
||||
digitalWrite(PIN_ENABLE, ENABLE_OFF);
|
||||
pinMode(PIN_HV_WDOG, OUTPUT);
|
||||
digitalWrite(PIN_HV_WDOG, LOW);
|
||||
pinMode(PIN_COL_LATCH, OUTPUT);
|
||||
pinMode(PIN_ROW_DATA, OUTPUT);
|
||||
pinMode(PIN_ROW_CLK, OUTPUT);
|
||||
|
||||
Serial.begin(115200);
|
||||
SPI.begin(PIN_DOT_CLK, -1, PIN_DOT_DATA, -1);
|
||||
SPI.setFrequency(4000000);
|
||||
SPI.setDataMode(SPI_MODE0);
|
||||
|
||||
makeTestPattern();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
scanFrame();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# KiCad project: PSU carrier board
|
||||
|
||||
Open `plasma_dmd_psu.kicad_pro` in KiCad 10.
|
||||
|
||||
The board carries the fuses, the HV watchdog, both regulators (copied from Stern 520-5138-00) and
|
||||
the connectors. The AliExpress modules sit **off-board** and connect through Phoenix 5.08 mm screw terminals:
|
||||
|
||||
| Connector | Goes to | Pins |
|
||||
|---|---|---|
|
||||
| J1 | 12 V DC brick | +12V, GND |
|
||||
| J3 | Mini560 5 V buck | IN+, IN−, OUT+, OUT− |
|
||||
| J4 | Relay module | VCC, GND, IN, COM, NO |
|
||||
| J5 | NCH6100HV #1 (set 90 V) | VIN, GND, HV+ |
|
||||
| J6 | Isolated DC-DC input | VIN, GND |
|
||||
| J7 | NCH6100HV #2 (set 130 V) output | HV+ (→ GND), GND (→ −130 V raw) |
|
||||
| J8 | ESP32 | 5V, GND, GPIO21 (watchdog) |
|
||||
| J2 | Panel power, Molex KK-396 8-pin | Stern CN2 order: 1 −112V, 2 −100V, 3 key, 4–5 GND, 6 +5V, 7 +12V (via JP1), 8 +65V |
|
||||
|
||||
Wire the isolated DC-DC output straight to NCH6100HV #2's input, off-board. Both float at −130 V.
|
||||
|
||||
> ⚠️ **J2 pinout is a placeholder.** It follows the Stern supply, not necessarily your WPC panel.
|
||||
> Map it to your panel before you plug anything in.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `gen_kicad.py` | Generates the schematic and the project's net classes from the `PARTS` table. **The source of truth.** |
|
||||
| `check_netlist.py` | Exports the netlist with kicad-cli and checks every pin against `PARTS` |
|
||||
| `plasma_dmd_psu.kicad_sch` | Generated schematic (labels-per-pin style: connectivity is by net label) |
|
||||
| `plasma_dmd_psu.pdf` | Plot of the schematic |
|
||||
|
||||
```bash
|
||||
python hardware/kicad/gen_kicad.py
|
||||
python hardware/kicad/check_netlist.py
|
||||
```
|
||||
|
||||
Re-running `gen_kicad.py` **overwrites the schematic**. Either keep editing `PARTS` and regenerate, or
|
||||
stop using the generator once you start editing the schematic in KiCad.
|
||||
|
||||
## Net classes (already in the project)
|
||||
|
||||
| Class | Nets | Clearance | Track |
|
||||
|---|---|---|---|
|
||||
| HV | +90V_RAW, +65V, −130V_RAW, −112V, −100V, HVP/HVN_MOD and all regulator internals | **3.0 mm** | 0.6 mm |
|
||||
| Power | GND, +12V*, +5V, +12V_PANEL | 0.3 mm | 1.0 mm |
|
||||
| Default | Watchdog signals | 0.25 mm | 0.4 mm |
|
||||
|
||||
## Making the PCB
|
||||
|
||||
1. Open the PCB editor and choose **Tools → Update PCB from Schematic** (F8). All footprints arrive with ratsnest.
|
||||
2. Draw the board outline on `Edge.Cuts`.
|
||||
3. Placement tips:
|
||||
- Keep the HV area (J5/J7, F4/F5, regulator, J2) on one side and the 12 V/5 V/watchdog on the other.
|
||||
- Leave room for TO-220 heatsinks on Q3, Q5 and Q6. R13 (5 W) runs warm.
|
||||
- Consider routed slots between the HV and low-voltage areas.
|
||||
4. Run DRC. The HV class enforces 3 mm to everything.
|
||||
|
||||
## Verified so far
|
||||
|
||||
- The schematic loads in KiCad 10.0.6, and ERC reports 0 violations.
|
||||
- `check_netlist.py`: all 122 pins sit on the intended nets (31 nets).
|
||||
- All symbols and footprints come from the KiCad 10 standard libraries.
|
||||
- Transistor pinouts match the footprints: MJE15030/31 are B-C-E (TO-220); MPSA42/92 are E-B-C (TO-92); 2N7000 is S-G-D.
|
||||
|
||||
**Not verified:** the circuit itself on real hardware, and the module current ratings.
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Export the netlist with kicad-cli and compare it with PARTS in gen_kicad.py.
|
||||
|
||||
python hardware/kicad/check_netlist.py [path/to/kicad-cli]
|
||||
"""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from gen_kicad import PARTS, PROJECT, pin_cache_for, read_sexpr_block
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
CLI = sys.argv[1] if len(sys.argv) > 1 else "C:/Users/patri/AppData/Local/Programs/KiCad/10.0/bin/kicad-cli.exe"
|
||||
net_file = HERE / f"{PROJECT}.net"
|
||||
subprocess.run([CLI, "sch", "export", "netlist", "-o", str(net_file), str(HERE / f"{PROJECT}.kicad_sch")],
|
||||
check=True, capture_output=True)
|
||||
text = net_file.read_text(encoding="utf-8")
|
||||
net_file.unlink()
|
||||
|
||||
# actual: (ref, pin number) -> net name
|
||||
actual = {}
|
||||
for m in re.finditer(r"\(net\s+\(code", text):
|
||||
blk = read_sexpr_block(text, m.start())
|
||||
name = re.search(r'\(name "([^"]*)"\)', blk).group(1).lstrip("/")
|
||||
for ref, pin in re.findall(r'\(node\s+\(ref "([^"]+)"\)\s+\(pin "([^"]+)"\)', blk):
|
||||
actual[(ref, pin)] = name
|
||||
|
||||
errors = 0
|
||||
expected = {}
|
||||
for ref, lib_id, value, fp, conns, g in PARTS:
|
||||
for num, name, *_ in pin_cache_for(lib_id):
|
||||
key = num if num in conns else (name if name in conns else None)
|
||||
if key is None or conns[key] is None:
|
||||
continue
|
||||
expected[(ref, num)] = conns[key]
|
||||
|
||||
for k, net in expected.items():
|
||||
got = actual.get(k)
|
||||
if got != net:
|
||||
errors += 1
|
||||
print(f"MISMATCH {k}: expected {net}, got {got}")
|
||||
|
||||
# every net should join at least two pins
|
||||
by_net = {}
|
||||
for k, net in actual.items():
|
||||
by_net.setdefault(net, []).append(k)
|
||||
for net, nodes in sorted(by_net.items()):
|
||||
if len(nodes) < 2 and not net.startswith("unconnected"):
|
||||
errors += 1
|
||||
print(f"SINGLE-PIN NET {net}: {nodes}")
|
||||
|
||||
print(f"{len(expected)} pins checked, {len(by_net)} nets, {errors} problem(s)")
|
||||
sys.exit(1 if errors else 0)
|
||||
@@ -0,0 +1,333 @@
|
||||
"""Generate the KiCad schematic for the Plasma DMD PSU carrier board.
|
||||
|
||||
The AliExpress modules (Mini560, 2x NCH6100HV, isolated DC-DC, relay) sit off-board and connect
|
||||
through screw terminals. Symbols are copied from the installed KiCad standard libraries.
|
||||
Every pin gets a short wire stub and a net label; connectivity is defined in PARTS below.
|
||||
|
||||
python hardware/kicad/gen_kicad.py [path/to/kicad/share]
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
PROJECT = "plasma_dmd_psu"
|
||||
SHARE = Path(sys.argv[1] if len(sys.argv) > 1 else "C:/Users/patri/AppData/Local/Programs/KiCad/10.0/share/kicad")
|
||||
SYMDIR = SHARE / "symbols"
|
||||
|
||||
FP_R = "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal"
|
||||
FP_R1W = "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P15.24mm_Horizontal"
|
||||
FP_R5W = "Resistor_THT:R_Axial_Power_L25.0mm_W6.4mm_P30.48mm"
|
||||
FP_D35 = "Diode_THT:D_DO-35_SOD27_P7.62mm_Horizontal"
|
||||
FP_D41 = "Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal"
|
||||
FP_D15 = "Diode_THT:D_DO-15_P12.70mm_Horizontal"
|
||||
FP_LED = "LED_THT:LED_D5.0mm"
|
||||
FP_TO92 = "Package_TO_SOT_THT:TO-92_Inline"
|
||||
FP_TO220 = "Package_TO_SOT_THT:TO-220-3_Vertical"
|
||||
FP_FUSE = "Fuse:Fuseholder_Cylinder-5x20mm_Schurter_0031_8201_Horizontal_Open"
|
||||
FP_CFILM = "Capacitor_THT:C_Rect_L18.0mm_W5.0mm_P15.00mm_FKS3_FKP3"
|
||||
FP_CDISC = "Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P5.00mm"
|
||||
FP_CP5 = "Capacitor_THT:CP_Radial_D5.0mm_P2.00mm"
|
||||
FP_CP63 = "Capacitor_THT:CP_Radial_D6.3mm_P2.50mm"
|
||||
FP_CP10 = "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm"
|
||||
FP_CP125 = "Capacitor_THT:CP_Radial_D12.5mm_P5.00mm"
|
||||
FP_HDR2 = "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical"
|
||||
FP_KK8 = "Connector_Molex:Molex_KK-396_A-41791-0008_1x08_P3.96mm_Vertical"
|
||||
FP_HOLE = "MountingHole:MountingHole_3.2mm_M3"
|
||||
|
||||
|
||||
def tb(n):
|
||||
return f"TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-{n}-5.08_1x{n:02d}_P5.08mm_Horizontal"
|
||||
|
||||
|
||||
# (ref, lib_id, value, footprint, {pin number or name: net}, group)
|
||||
PARTS = [
|
||||
# 1. Input and +5 V
|
||||
("J1", "Connector:Screw_Terminal_01x02", "12V DC in", tb(2), {"1": "+12V_IN", "2": "GND"}, 0),
|
||||
("F1", "Device:Fuse", "3A T", FP_FUSE, {"1": "+12V_IN", "2": "+12V"}, 0),
|
||||
("D1", "Device:D_TVS", "P6KE15A", FP_D15, {"1": "+12V", "2": "GND"}, 0),
|
||||
("C1", "Device:C_Polarized", "470uF 25V", FP_CP10, {"1": "+12V", "2": "GND"}, 0),
|
||||
("J3", "Connector:Screw_Terminal_01x04", "Mini560 5V: IN+ IN- OUT+ OUT-", tb(4),
|
||||
{"1": "+12V", "2": "GND", "3": "+5V", "4": "GND"}, 0),
|
||||
("C2", "Device:C_Polarized", "100uF 10V", FP_CP63, {"1": "+5V", "2": "GND"}, 0),
|
||||
# 2. HV enable + watchdog
|
||||
("J8", "Connector:Screw_Terminal_01x03", "ESP32: 5V GND GPIO21", tb(3),
|
||||
{"1": "+5V", "2": "GND", "3": "WDOG_IN"}, 1),
|
||||
("C3", "Device:C", "100nF", FP_CDISC, {"1": "WDOG_IN", "2": "WDOG_A"}, 1),
|
||||
("D3", "Device:D", "1N4148", FP_D35, {"K": "WDOG_A", "A": "GND"}, 1),
|
||||
("D2", "Device:D", "1N4148", FP_D35, {"A": "WDOG_A", "K": "WDOG_B"}, 1),
|
||||
("C4", "Device:C_Polarized", "10uF 16V", FP_CP5, {"1": "WDOG_B", "2": "GND"}, 1),
|
||||
("R1", "Device:R", "100k", FP_R, {"1": "WDOG_B", "2": "GND"}, 1),
|
||||
("Q1", "Transistor_FET:2N7000", "2N7000", FP_TO92, {"G": "WDOG_B", "S": "GND", "D": "RELAY_IN"}, 1),
|
||||
("J4", "Connector:Screw_Terminal_01x05", "Relay module: VCC GND IN COM NO", tb(5),
|
||||
{"1": "+5V", "2": "GND", "3": "RELAY_IN", "4": "+12V", "5": "+12V_HV"}, 1),
|
||||
("F2", "Device:Fuse", "1A T", FP_FUSE, {"1": "+12V_HV", "2": "+12V_HVP"}, 1),
|
||||
("F3", "Device:Fuse", "1A T", FP_FUSE, {"1": "+12V_HV", "2": "+12V_HVN"}, 1),
|
||||
# 3. Positive HV
|
||||
("J5", "Connector:Screw_Terminal_01x03", "NCH6100HV #1 (90V): VIN GND HV+", tb(3),
|
||||
{"1": "+12V_HVP", "2": "GND", "3": "HVP_MOD"}, 2),
|
||||
("F4", "Device:Fuse", "100mA F", FP_FUSE, {"1": "HVP_MOD", "2": "+90V_RAW"}, 2),
|
||||
("C5", "Device:C_Polarized", "22uF 250V", FP_CP125, {"1": "+90V_RAW", "2": "GND"}, 2),
|
||||
("R2", "Device:R", "330k", FP_R, {"1": "+90V_RAW", "2": "GND"}, 2),
|
||||
("R3", "Device:R", "100k 1W", FP_R1W, {"1": "+90V_RAW", "2": "N_LED1"}, 2),
|
||||
("LED1", "Device:LED", "HV on", FP_LED, {"A": "N_LED1", "K": "GND"}, 2),
|
||||
("D4", "Device:D_Zener", "1N5228 3.9V", FP_D35, {"K": "+90V_RAW", "A": "REF_P"}, 2),
|
||||
("R5", "Device:R", "47k 1W", FP_R1W, {"1": "REF_P", "2": "GND"}, 2),
|
||||
("R4", "Device:R", "1.5k", FP_R, {"1": "+90V_RAW", "2": "Q2_E"}, 2),
|
||||
("Q2", "Transistor_BJT:Q_PNP_EBC", "MPSA92", FP_TO92, {"E": "Q2_E", "B": "REF_P", "C": "Q3_B"}, 2),
|
||||
("D5", "Device:D_Zener", "1N4760A 68V", FP_D41, {"K": "Q3_B", "A": "GND"}, 2),
|
||||
("Q3", "Transistor_BJT:Q_NPN_BCE", "MJE15030", FP_TO220, {"C": "+90V_RAW", "B": "Q3_B", "E": "+65V"}, 2),
|
||||
("C6", "Device:C", "0.1uF 500V", FP_CFILM, {"1": "+65V", "2": "GND"}, 2),
|
||||
("R6", "Device:R", "330k", FP_R, {"1": "+65V", "2": "GND"}, 2),
|
||||
# 4. Negative HV
|
||||
("J6", "Connector:Screw_Terminal_01x02", "Isolated DC-DC in: VIN GND", tb(2),
|
||||
{"1": "+12V_HVN", "2": "GND"}, 3),
|
||||
("J7", "Connector:Screw_Terminal_01x02", "NCH6100HV #2 (130V) out: HV+ GND", tb(2),
|
||||
{"1": "GND", "2": "HVN_MOD"}, 3),
|
||||
("F5", "Device:Fuse", "100mA F", FP_FUSE, {"1": "HVN_MOD", "2": "-130V_RAW"}, 3),
|
||||
("C7", "Device:C_Polarized", "22uF 250V", FP_CP125, {"1": "GND", "2": "-130V_RAW"}, 3),
|
||||
("R7", "Device:R", "330k", FP_R, {"1": "GND", "2": "-130V_RAW"}, 3),
|
||||
("R8", "Device:R", "100k 1W", FP_R1W, {"1": "N_LED2", "2": "-130V_RAW"}, 3),
|
||||
("LED2", "Device:LED", "HV on", FP_LED, {"A": "GND", "K": "N_LED2"}, 3),
|
||||
("D6", "Device:D_Zener", "1N5228 3.9V", FP_D35, {"K": "REF_N", "A": "-130V_RAW"}, 3),
|
||||
("R10", "Device:R", "47k 1W", FP_R1W, {"1": "GND", "2": "REF_N"}, 3),
|
||||
("R9", "Device:R", "1.5k", FP_R, {"1": "Q4_E", "2": "-130V_RAW"}, 3),
|
||||
("Q4", "Transistor_BJT:Q_NPN_EBC", "MPSA42", FP_TO92, {"E": "Q4_E", "B": "REF_N", "C": "Q5_B"}, 3),
|
||||
("D7", "Device:D_Zener", "1N4743 13V", FP_D41, {"K": "REF_M", "A": "Q5_B"}, 3),
|
||||
("D8", "Device:D_Zener", "1N4764A 100V", FP_D41, {"K": "GND", "A": "REF_M"}, 3),
|
||||
("Q5", "Transistor_BJT:Q_PNP_BCE", "MJE15031", FP_TO220, {"B": "Q5_B", "C": "-130V_RAW", "E": "-112V"}, 3),
|
||||
("C8", "Device:C", "0.1uF 500V", FP_CFILM, {"1": "GND", "2": "-112V"}, 3),
|
||||
("R11", "Device:R", "330k", FP_R, {"1": "GND", "2": "-112V"}, 3),
|
||||
("R12", "Device:R", "1.5k", FP_R, {"1": "REF_M", "2": "Q6_B"}, 3),
|
||||
("Q6", "Transistor_BJT:Q_NPN_BCE", "MJE15030", FP_TO220, {"B": "Q6_B", "C": "Q6_C", "E": "-100V"}, 3),
|
||||
("R13", "Device:R", "2k 5W", FP_R5W, {"1": "GND", "2": "Q6_C"}, 3),
|
||||
("C9", "Device:C", "0.1uF 500V", FP_CFILM, {"1": "GND", "2": "-100V"}, 3),
|
||||
# 5. Panel connector (Stern 520-5138 CN2 order; verify against your panel!)
|
||||
("J2", "Connector_Generic:Conn_01x08", "Panel power (Stern CN2 order)", FP_KK8,
|
||||
{"1": "-112V", "2": "-100V", "3": None, "4": "GND", "5": "GND", "6": "+5V", "7": "+12V_PANEL", "8": "+65V"}, 4),
|
||||
("JP1", "Connector_Generic:Conn_01x02", "Fit if panel uses +12V", FP_HDR2,
|
||||
{"1": "+12V", "2": "+12V_PANEL"}, 4),
|
||||
("H1", "Mechanical:MountingHole", "M3", FP_HOLE, {}, 4),
|
||||
("H2", "Mechanical:MountingHole", "M3", FP_HOLE, {}, 4),
|
||||
("H3", "Mechanical:MountingHole", "M3", FP_HOLE, {}, 4),
|
||||
("H4", "Mechanical:MountingHole", "M3", FP_HOLE, {}, 4),
|
||||
]
|
||||
|
||||
GROUP_TITLES = [
|
||||
"1. 12 V input and +5 V (Mini560 off-board)",
|
||||
"2. HV enable relay + hardware watchdog (relay module off-board)",
|
||||
"3. Positive HV: NCH6100HV #1 off-board + original regulator -> +65 V",
|
||||
"4. Negative HV: isolated DC-DC + NCH6100HV #2 off-board (FLOATING at -130 V) + regulator -> -112 / -100 V",
|
||||
"5. Panel connector and mounting",
|
||||
]
|
||||
COLS = 7 # parts per row inside a group
|
||||
DX, DY = 50.8, 27.94
|
||||
GROUP_GAP = 12.7
|
||||
|
||||
|
||||
def group_origins():
|
||||
"""Stack the groups vertically, each as tall as its number of part rows."""
|
||||
y, out = 33.02, []
|
||||
for g in range(len(GROUP_TITLES)):
|
||||
n = sum(1 for p in PARTS if p[5] == g)
|
||||
out.append((25.4, y))
|
||||
y += 12.7 + -(-n // COLS) * DY + GROUP_GAP
|
||||
return out
|
||||
|
||||
|
||||
GROUPS = list(zip(GROUP_TITLES, group_origins()))
|
||||
|
||||
NOTES = """Plasma DMD - 12 V DC PSU carrier board (rev 0.1, unverified)
|
||||
Connectivity is by net labels: every pin has a stub + label. Off-board modules connect via J3-J7.
|
||||
Panel voltages are set by zeners D5 (68 V -> about +65 V) and D7+D8 (113 V -> about -112 / -100 V), copied from Stern 520-5138-00.
|
||||
SAFETY: keep >= 3 mm clearance (more is better) between HV nets (+90V_RAW, +65V, -130V_RAW, -112V, -100V, HVN_MOD, HVP_MOD) and low-voltage nets.
|
||||
J2 pin order follows the Stern CN2 connector: map it to YOUR panel before use. Pin 3 is the key position."""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
def uid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def read_sexpr_block(text, start):
|
||||
depth, i = 0, start
|
||||
in_str = False
|
||||
while True:
|
||||
ch = text[i]
|
||||
if in_str:
|
||||
if ch == "\\":
|
||||
i += 1
|
||||
elif ch == '"':
|
||||
in_str = False
|
||||
elif ch == '"':
|
||||
in_str = True
|
||||
elif ch == "(":
|
||||
depth += 1
|
||||
elif ch == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return text[start:i + 1]
|
||||
i += 1
|
||||
|
||||
|
||||
_libcache = {}
|
||||
|
||||
|
||||
def lib_symbol(lib_id):
|
||||
lib, name = lib_id.split(":")
|
||||
if lib not in _libcache:
|
||||
_libcache[lib] = (SYMDIR / f"{lib}.kicad_sym").read_text(encoding="utf-8")
|
||||
text = _libcache[lib]
|
||||
m = re.search(r'\(symbol "' + re.escape(name) + r'"', text)
|
||||
if not m:
|
||||
raise KeyError(lib_id)
|
||||
block = read_sexpr_block(text, m.start())
|
||||
ext = re.search(r'\(extends "([^"]+)"\)', block)
|
||||
if ext:
|
||||
raise ValueError(f"{lib_id} extends {ext.group(1)}; pick a non-derived symbol")
|
||||
# top-level name gets the library prefix; sub-units keep the bare name
|
||||
return block.replace(f'(symbol "{name}"', f'(symbol "{lib_id}"', 1)
|
||||
|
||||
|
||||
def pins_of(block):
|
||||
"""Return list of (number, name, x, y, angle) from a symbol block (symbol coords, y up)."""
|
||||
out = []
|
||||
for m in re.finditer(r'\(pin \w+ \w+\s*\(at ([-\d.]+) ([-\d.]+) ([-\d.]+)\)', block):
|
||||
pb = read_sexpr_block(block, m.start())
|
||||
name = re.search(r'\(name "([^"]*)"', pb).group(1)
|
||||
num = re.search(r'\(number "([^"]*)"', pb).group(1)
|
||||
out.append((num, name, float(m.group(1)), float(m.group(2)), float(m.group(3))))
|
||||
return out
|
||||
|
||||
|
||||
def pin_cache_for(lib_id):
|
||||
return pins_of(lib_symbol(lib_id))
|
||||
|
||||
|
||||
def q(s):
|
||||
return '"' + s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + '"'
|
||||
|
||||
|
||||
def snap(v):
|
||||
return round(round(v / 1.27) * 1.27, 2)
|
||||
|
||||
|
||||
HV_NETS = ["+90V_RAW", "+65V", "HVP_MOD", "Q2_E", "REF_P", "Q3_B", "N_LED1",
|
||||
"-130V_RAW", "-112V", "-100V", "HVN_MOD", "Q4_E", "REF_N", "Q5_B", "REF_M", "Q6_B", "Q6_C", "N_LED2"]
|
||||
POWER_NETS = ["GND", "+12V", "+12V_IN", "+12V_HV", "+12V_HVP", "+12V_HVN", "+5V", "+12V_PANEL"]
|
||||
|
||||
|
||||
def netclass(name, clearance, track, priority):
|
||||
return {"bus_width": 12, "clearance": clearance, "diff_pair_gap": 0.25, "diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2, "line_style": 0, "microvia_diameter": 0.3, "microvia_drill": 0.1,
|
||||
"name": name, "pcb_color": "rgba(0, 0, 0, 0.000)", "priority": priority,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)", "track_width": track, "tuning_profile": "",
|
||||
"via_diameter": 1.0, "via_drill": 0.5, "wire_width": 6}
|
||||
|
||||
|
||||
def write_project():
|
||||
"""Create/update the .kicad_pro with net classes. HV nets get 3 mm clearance to everything."""
|
||||
import json
|
||||
pro = HERE / f"{PROJECT}.kicad_pro"
|
||||
data = json.loads(pro.read_text(encoding="utf-8")) if pro.exists() else {}
|
||||
data.setdefault("meta", {"filename": pro.name, "version": 3})
|
||||
data["net_settings"] = {
|
||||
"classes": [
|
||||
netclass("Default", 0.25, 0.4, 2147483647),
|
||||
netclass("Power", 0.3, 1.0, 1),
|
||||
netclass("HV", 3.0, 0.6, 0),
|
||||
],
|
||||
"meta": {"version": 5},
|
||||
"net_colors": None,
|
||||
"netclass_assignments": None,
|
||||
"netclass_patterns": [{"netclass": "HV", "pattern": "/" + n} for n in HV_NETS]
|
||||
+ [{"netclass": "Power", "pattern": "/" + n} for n in POWER_NETS],
|
||||
}
|
||||
pro.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
root = uid()
|
||||
lib_blocks, pin_cache = {}, {}
|
||||
for p in PARTS:
|
||||
if p[1] not in lib_blocks:
|
||||
blk = lib_symbol(p[1])
|
||||
lib_blocks[p[1]] = blk
|
||||
pin_cache[p[1]] = pins_of(blk)
|
||||
|
||||
items = []
|
||||
counts = {}
|
||||
for ref, lib_id, value, fp, conns, g in PARTS:
|
||||
i = counts.get(g, 0)
|
||||
counts[g] = i + 1
|
||||
gx, gy = GROUPS[g][1]
|
||||
x = snap(gx + 12.7 + (i % COLS) * DX)
|
||||
y = snap(gy + 12.7 + (i // COLS) * DY)
|
||||
pins = pin_cache[lib_id]
|
||||
# Ref/value go beside the part: above it when a net label sticks out to the right
|
||||
# (horizontal two-pin parts), otherwise to the right of the body.
|
||||
if any(int(a) % 360 == 180 for *_, a in pins):
|
||||
rx, ry, vx, vy, just = x, snap(y - 6.35), x, snap(y - 4.445), ""
|
||||
else:
|
||||
rx, ry, vx, vy, just = snap(x + 5.08), snap(y - 1.27), snap(x + 5.08), snap(y + 1.27), " (justify left)"
|
||||
pin_uuid = "\n".join(f' (pin {q(num)} (uuid {q(uid())}))' for num, *_ in pins)
|
||||
items.append(f""" (symbol (lib_id {q(lib_id)}) (at {x} {y} 0) (unit 1)
|
||||
(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (uuid {q(uid())})
|
||||
(property "Reference" {q(ref)} (at {rx} {ry} 0) (effects (font (size 1.27 1.27)){just}))
|
||||
(property "Value" {q(value)} (at {vx} {vy} 0) (effects (font (size 1.27 1.27)){just}))
|
||||
(property "Footprint" {q(fp)} (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
{pin_uuid}
|
||||
(instances (project {q(PROJECT)} (path {q('/' + root)} (reference {q(ref)}) (unit 1))))
|
||||
)""")
|
||||
used = set()
|
||||
for num, name, px, py, ang in pins:
|
||||
key = num if num in conns else (name if name in conns else None)
|
||||
if key is None:
|
||||
if conns:
|
||||
raise KeyError(f"{ref}: pin {num}/{name} not connected in PARTS")
|
||||
continue
|
||||
used.add(key)
|
||||
net = conns[key]
|
||||
cx, cy = round(x + px, 2), round(y - py, 2) # schematic y is down
|
||||
# stub points away from the body (pin angle points from connection point toward body)
|
||||
dx, dy = {0: (-1, 0), 90: (0, 1), 180: (1, 0), 270: (0, -1)}[int(ang) % 360]
|
||||
ex, ey = round(cx + dx * 2.54, 2), round(cy + dy * 2.54, 2)
|
||||
if net is None:
|
||||
items.append(f' (no_connect (at {cx} {cy}) (uuid {q(uid())}))')
|
||||
continue
|
||||
items.append(f' (wire (pts (xy {cx} {cy}) (xy {ex} {ey})) (stroke (width 0) (type default)) (uuid {q(uid())}))')
|
||||
lang, just = {(-1, 0): (180, "right"), (1, 0): (0, "left"),
|
||||
(0, -1): (90, "left"), (0, 1): (270, "right")}[(dx, dy)]
|
||||
items.append(f' (label {q(net)} (at {ex} {ey} {lang}) (fields_autoplaced yes)'
|
||||
f' (effects (font (size 1.27 1.27)) (justify {just} bottom)) (uuid {q(uid())}))')
|
||||
missing = set(conns) - used
|
||||
if missing:
|
||||
raise KeyError(f"{ref}: unknown pins {missing}")
|
||||
|
||||
for title, (gx, gy) in GROUPS:
|
||||
items.append(f' (text {q(title)} (exclude_from_sim no) (at {gx} {gy} 0)'
|
||||
f' (effects (font (size 2.54 2.54) (bold yes)) (justify left bottom)) (uuid {q(uid())}))')
|
||||
items.append(f' (text {q(NOTES)} (exclude_from_sim no) (at 25.4 12.7 0)'
|
||||
f' (effects (font (size 1.778 1.778)) (justify left top)) (uuid {q(uid())}))')
|
||||
|
||||
libs = "\n".join(" " + b.replace("\n", "\n ") for b in lib_blocks.values())
|
||||
sch = f"""(kicad_sch (version 20260101) (generator "plasma_dmd_gen") (generator_version "1.0")
|
||||
(uuid {q(root)})
|
||||
(paper "A2")
|
||||
(title_block (title "Plasma DMD 12 V DC PSU") (rev "0.1") (comment 1 "Unverified - see notes"))
|
||||
(lib_symbols
|
||||
{libs}
|
||||
)
|
||||
{chr(10).join(items)}
|
||||
(sheet_instances (path "/" (page "1")))
|
||||
)
|
||||
"""
|
||||
(HERE / f"{PROJECT}.kicad_sch").write_text(sch, encoding="utf-8")
|
||||
write_project()
|
||||
print(f"wrote {PROJECT}.kicad_sch with {len(PARTS)} parts")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"meta": {
|
||||
"filename": "plasma_dmd_psu.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.25,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.4,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 1.0,
|
||||
"via_drill": 0.5,
|
||||
"wire_width": 6
|
||||
},
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.3,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Power",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 1,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 1.0,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 1.0,
|
||||
"via_drill": 0.5,
|
||||
"wire_width": 6
|
||||
},
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 3.0,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "HV",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 0,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.6,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 1.0,
|
||||
"via_drill": 0.5,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 5
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": [
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/+90V_RAW"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/+65V"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/HVP_MOD"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/Q2_E"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/REF_P"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/Q3_B"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/N_LED1"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/-130V_RAW"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/-112V"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/-100V"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/HVN_MOD"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/Q4_E"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/REF_N"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/Q5_B"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/REF_M"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/Q6_B"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/Q6_C"
|
||||
},
|
||||
{
|
||||
"netclass": "HV",
|
||||
"pattern": "/N_LED2"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/GND"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V_IN"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V_HV"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V_HVP"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V_HVN"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+5V"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V_PANEL"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
File diff suppressed because it is too large.
Load diff
|
After Width: | Height: | Size: 304 KiB |
@@ -0,0 +1,319 @@
|
||||
"""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")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Reference in new issue
Block a user