Heatsinks, wider 12 V tracks, design review; pause project

- PCB rev 0.1: Power3A class (1.5 mm) for the 12 V input path, Aavid
  clip-on heatsinks on Q3/Q5/Q6 (project footprint library), T-junction
  healing and via shrink in the router; DRC 0 errors
- Current-capacity and heatsink documentation
- CLAUDE.md with project decisions, workflow and gotchas
- docs/design-review.md: review of a module-only PSU (rev 0.2) and cost
  estimate
- Project paused for budget and safety reasons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-24 15:07:58 +02:00
1 parent 28c666506e
commit cdf24e0b4b
15 files changed
+5917 -4730

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
# Plasma DMD — notes for Claude
The goal: drive a second-hand 128×32 pinball **plasma** DMD panel standalone from a **Wemos D1 mini ESP32**,
powered from **12 V DC only**. The panel was originally on a Williams WPC controller and was bought on
2021-09-18 for €37. No original controller or PSU came with it.
## Status (keep this section up to date)
- **PAUSED (2026-10-24)** at the user's request, because of the cost and the safety risk. If resuming, consider the
cheaper, no-high-voltage alternative discussed: a 128×32 HUB75 LED matrix driven by the same ESP32 (about €30–50).
- Design docs in `docs/`, PSU schematic and routed PCB rev 0.1 in `hardware/kicad/`, firmware skeleton in `firmware/`.
- Not yet known: the panel's make and part number, its power and data connector pinouts, and the
polarity of its control signals. **J2's pinout is a placeholder (Stern CN2 order).**
- Nothing is built or tested on real hardware.
- **Proposed rev 0.2: module-only PSU** (no linear regulators or heatsinks), reviewed in `docs/design-review.md`.
It's waiting on the must-fix items M1–M4 there. The schematic and PCB are still rev 0.1.
## Design decisions (don't silently change these)
- **PSU:**
- A 12 V brick feeds a Mini560 (5 V) and two NCH6100HV boost modules: one at 90 V, and one at
130 V fed through an isolated DC-DC, so its output floats and becomes −130 V.
- Those raw rails feed a copy of the regulator section of the Stern 520-5138-00 (zener-referenced
followers), giving about +65 V, −112 V and −100 V.
- The zeners set the panel voltages, not the module trim pots.
- **Modules sit off-board** on screw terminals. The PCB is a carrier board.
- **Safety first:**
- A relay switches the 12 V feed to the HV modules.
- The relay is driven by a hardware charge-pump watchdog, toggled by GPIO21 once per scanned row
(about 2 kHz). If toggling stops, the HV drops.
- Bleeders and fuses on every HV rail.
- **Clearances** (`hardware/kicad/plasma_dmd_psu.kicad_dru`):
- 3 mm between HV and LV, 1.3 mm between HV nets.
- Documented exceptions only inside or next to transistor and connector pins.
- **TO-220 tabs and heatsinks are live.** Each heatsink pin is on its collector net.
- **Track widths** come from IPC-2221 against the fuse currents (see `hardware/kicad/README.md`).
## Workflow and tools
- KiCad 10: `C:/Users/patri/AppData/Local/Programs/KiCad/10.0/bin/` (`kicad-cli.exe`, and `python.exe` with `pcbnew`).
- Freerouting 2.5.0 jar: `tools/freerouting/`, git-ignored. It needs **Java 25**:
`C:/Program Files/Eclipse Adoptium/jre-25.0.4.101-hotspot/bin/java.exe`.
- Pipeline (KiCad must be closed while the scripts run):
1. `python hardware/kicad/gen_kicad.py` writes the schematic and the project net classes from `PARTS`.
It **keeps symbol UUIDs**, so the PCB stays linked.
2. `python hardware/kicad/check_netlist.py` verifies every pin's net through kicad-cli.
3. `<kicad python> hardware/kicad/layout_pcb.py` adds, swaps and places footprints from `PLACE` and draws
the outline and silkscreen. **It deletes tracks and zones.**
4. `<kicad python> hardware/kicad/route_pcb.py [passes]` runs Freerouting:
- It routes with oversized tracks and vias, then shrinks them, which yields the clearances.
- It adds J2 escape stubs and heals T-junctions.
- It adds the bottom GND pour.
5. `kicad-cli pcb drc --schematic-parity --refill-zones hardware/kicad/plasma_dmd_psu.kicad_pcb`.
The target is 0 errors. The 9 silkscreen warnings (TO-220 outline over the heatsink pins) are known.
- Once the user starts hand-editing the schematic or PCB in KiCad, stop regenerating with the scripts.
## Gotchas learned the hard way
- Bash heredocs passed through the tool can collapse `\\` and `\n` escapes. For Python with regexes
or escapes, use the Write/Edit tools, not `python - <<EOF`.
- pcbnew: use `board.Delete(item)`, not `Remove` (Remove leads to segfaults). `GetTracks()` throws
`TypeError` when empty. Copy coordinates out of an item before deleting it.
- KiCad schematic text needs `\n` escapes, not raw newlines, or the file won't load.
- Freerouting puts fixed stubs back in the SES (duplicates). Its SES can silently omit a net when routing
is incomplete, so always check DRC for unconnected pads.
- MKDS terminal blocks: wire entry is on the footprint's +y side.
## User preferences
- Commit only when the user asks. Initial commit `dbc1f00`, PCB rev 0.1 `28c6665`. The repo has no remote.
- Before changing a file the user may have edited, check their edits first and keep them.
+5
View File
@@ -24,11 +24,16 @@ Stern/Sega display PSU (520-5138-00). The original transformer and AC front end
| [docs/safety.md](docs/safety.md) | Safety design (HV watchdog, fusing, layout, bring-up procedure) | | [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/esp32-interface.md](docs/esp32-interface.md) | ESP32 pin plan, 74HCT245 level shifter, panel signals |
| [docs/bom.md](docs/bom.md) | Bill of materials | | [docs/bom.md](docs/bom.md) | Bill of materials |
| [docs/design-review.md](docs/design-review.md) | Design review of the module-only PSU (rev 0.2) and cost estimate |
| [reference/](reference/) | Original Stern 520-5138-00 display PSU schematic | | [reference/](reference/) | Original Stern 520-5138-00 display PSU schematic |
| [firmware/](firmware/) | PlatformIO project for the ESP32 | | [firmware/](firmware/) | PlatformIO project for the ESP32 |
| [CLAUDE.md](CLAUDE.md) | Project notes for Claude: decisions, workflow, tool paths, gotchas |
## Status ## Status
> **Paused (2026-10-24)** because of budget and safety concerns. See [docs/design-review.md](docs/design-review.md)
> for the latest review and cost estimate.
- [ ] Identify panel: manufacturer/part number from its label - [ ] 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 power connector pins (WPC panel ≠ Stern CN2 pinout) and check if +12 V is used
- [ ] Map the panel's 14-pin data connector - [ ] Map the panel's 14-pin data connector
+1 -1
View File
@@ -33,7 +33,7 @@ Prices are rough estimates.
| 2 | 330 kΩ ½ W | R6, R11 | R6, R7 | | 2 | 330 kΩ ½ W | R6, R11 | R6, R7 |
| 1 | 2 kΩ 5 W | R13 | R8 | | 1 | 2 kΩ 5 W | R13 | R8 |
| 3 | 0.1 µF 500 V film | C6, C8, C9 | C3, C4, C7 | | 3 | 0.1 µF 500 V film | C6, C8, C9 | C3, C4, C7 |
| 3 | TO-220 heatsink | for Q3, Q5, Q6 | | | 3 | Aavid 576802B03900G clip-on heatsink (~20 K/W), **live**: one per transistor, must not touch | HS1–HS3 on Q3, Q5, Q6 | |
## Additions ## Additions
+191
View File
@@ -0,0 +1,191 @@
# Design review and cost estimate
*Review date 2026-10-24. Covers the proposed **module-only** PSU (rev 0.2), compared with the routed rev 0.1
board, which used linear regulators copied from the Stern supply.*
## Verdict
The module-only design is **a better fit for this project** than rev 0.1:
- No linear losses, no transistors, no heatsinks.
- About 20 fewer custom PCB parts.
- A board roughly half the size.
- It also makes the −100 V / −112 V relationship more robust (see §3).
It is **not ready to build yet**. Four must-fix items (§8) block it: the unknown panel pinout, a weak point
in the watchdog drive, TVS clamp levels that depend on the panel's ratings, and buying genuine modules.
Estimated cost: **about €90–190, typically about €120** (§9).
---
## 1. Scope and assumptions
- **Panel:** a 128×32 plasma DMD from a WPC game. Make, part number and pinouts are **still unknown**.
- **Rails needed** (PinWiki / Stern supply): about +62–65 V, −112 V, and −100 V. The −100 V rail is a
fixed 12 V offset from −112 V. Plus +5 V logic, and +12 V if the panel uses it.
- **Currents are unknown.** The design is sized against the fuse limits. Real currents get measured at bring-up.
- **Use:** a hobby device in a closed enclosure. It is not a certified product.
## 2. Architecture (rev 0.2, module-only)
```
12 V brick (certified) ─F1 3A─┬─ TVS ─ Mini560 ──────────────── +5 V ─PTC─ TVS ─► ESP32, 74HCT245, panel logic
├──────────────────────────────── +12 V ─JP1─► panel (if used)
└─ relay (watchdog) ─ +12V_HV ─┬─F2─ 45–390 V boost (65 V) ─F4─ RC ─ TVS ─► +65 V
└─F3─ iso 12→12 V ─ NCH6100HV (112 V, + to GND)
─F5─ RC ─ TVS ─► −112 V
└─ B1212S stacked on −112 V ─ min-load ─► −100 V
```
## 3. Rail-by-rail review
| Rail | Source | Regulation | Protection | Findings |
|---|---|---|---|---|
| +5 V | Mini560, fixed 5 V | Good (buck, fixed) | Add a PTC (about 1.5 A) and an SMBJ5.0A | Without the TVS, an HV-to-5V fault reaches the ESP32 and USB |
| +12 V | Brick, via F1 | Brick | F1 3 A, P6KE15A | OK |
| +65 V | 45–390 V boost board (8–32 V in, ≤0.2 A, 40 W) | Regulated, but uses **burst mode** at light load (jumpy ripple) | F4 100 mA, TVS clamp at about 72–78 V, RC filter, bleeder | Module is oversized but the most common one that reaches 65 V. **Bench-test the ripple and the no-load voltage** before connecting the panel. |
| −112 V | Isolated 12→12 V (about 6 W) + NCH6100HV at 112 V, floating (+ to GND) | NCH6100HV regulates | F5 100 mA, TVS clamp (P6KE130A class, check against the panel maximum), RC, bleeder | The 6 W isolated converter limits this rail to about **40–45 mA**. Check against the measured current. |
| −100 V | B1212S-1W stacked: − on −112 V, + gives −100 V | **Unregulated**, about ±10% → offset 10.8–13.2 V | 15 V zener across it, minimum-load resistor (about 10% load) | **Advantage:** the 12 V offset holds during start-up and shutdown too, because both rails move together. Rev 0.1 couldn't guarantee that. Consider a regulated 1–2 W isolated module if the offset tolerance matters. |
Current direction checked: in the original circuit, Q6 *sourced* current into the −100 V node from GND, and the
panel returns it at −112 V. The B1212S sources current from its + output into −100 V, and it returns to its −
output at −112 V. Same direction, so the B1212S is never back-driven.
## 4. Safety review
Under IEC 62368-1 (AV/ICT equipment), anything above 120 V DC is class **ES3**, the highest-hazard class.
This design has −112 V, and up to about 180 V between +65 V and −112 V.
| # | Topic | Status |
|---|---|---|
| 4.1 | Mains isolation | OK. Provided by the certified brick (Class II). |
| 4.2 | HV and LV **share one ground** (same as the original pinball supplies) | Only the 3 mm spacing (basic insulation, about 2.5 mm needed at 250 V, pollution degree 2) protects the 5 V side. **Needs:** the TVS on +5 V (a fault blows F4/F5), and no USB connected while HV is on, or a USB isolator. |
| 4.3 | Exposed HV | The HV modules (including the floating isolated converter output and the NCH6100HV at −112 V) and **the panel's own driver boards** are live. **Needs a tool-only enclosure with HV labels.** |
| 4.4 | Fault current | Each HV rail has a 100 mA fast fuse, and the modules limit current. A TVS clamp plus fuse turns a runaway module into a blown fuse, not a damaged panel. |
| 4.5 | Stored charge | Bleeders discharge the rails to below 60 V within about 2 s. |
| 4.6 | Fail-safe HV enable | The relay is normally open and driven by the watchdog. **But see 6.1: the drive is weak.** |
| 4.7 | Live heatsinks | **Gone** in rev 0.2 (no linear regulators). |
| 4.8 | Counterfeit modules | The NCH6100HV designer warns that the fakes are poor quality and **a fire hazard**. Buy from an authorized seller. |
## 5. Thermal and efficiency
| | rev 0.1 (linear) | rev 0.2 (modules) |
|---|---|---|
| Regulator losses at 20 mA per rail | about 0.5 W (Q3) + 0.4 W (Q5) + up to 2 W (Q6/R13) | 0 (only about 20 mW in the RC filters) |
| Worst case (fuse-limited) | about 10 W of heat, plus 3 heatsinks | Only module losses |
| Total heat, typical | about 4–6 W | about 1–3 W (boost modules at about 80–88% efficiency, isolated chain at about 70%) |
| Heatsinks / active cooling | 3 heatsinks, no fan | **None needed**. Ventilation slots are enough. |
Power budget, typical: about 3–5 W for the panel HV, about 1–2 W for panel logic and the ESP32, plus losses.
That's about **8–12 W from the brick (≈ 1 A)**. The 36 W / 3 A brick has plenty of margin.
## 6. Control and firmware
1. **The watchdog gate drive is marginal (must fix).**
- The charge pump runs from the ESP32's 3.3 V output. After two diode drops it gives about 2.1–2.3 V at the 2N7000 gate.
- The 2N7000's turn-on threshold ranges from 0.8 to **3.0 V**. Some parts will never switch the relay module (about 5–10 mA).
- That fails safe (the HV stays off), but it isn't reliable.
- **Fix (no extra parts):** feed the charge pump from a spare 74HCT245 channel, which swings 5 V. That gives about 3.8 V at the gate.
- **Alternative:** replace the 2N7000 with a 2N3904/BC547 and a 10 kΩ base resistor.
2. The watchdog drop-out is about 1 s (C4 × R1). Using C4 = 1 µF gives about 0.1 s, which is less time with a stuck row on the panel.
3. **GPIO16/17** are free on ESP32-WROOM modules, but are used for PSRAM on WROVER modules. Check which module your Wemos board has.
4. The firmware scans in a busy loop for now. Wi-Fi interrupts will cause jitter and flicker. Later, move the scanning to a high-priority task, or to I2S/DMA output.
5. The panel signal polarities (enable, latch, row data) are still unverified.
## 7. PCB and mechanics
- **rev 0.1** (170 × 125 mm, routed, DRC clean) is obsolete if rev 0.2 is adopted.
- **rev 0.2 estimate:** about 100 × 80 mm.
- On the board: terminal blocks, 5 fuses, TVS clamps, RC filters, bleeders, the watchdog, the 74HCT245, the B1212S (SIP-4) and the panel connector.
- Off the board: the Mini560, the boost modules, the isolated converter and the relay module.
- Keep the 3 mm HV/LV spacing, the DRC rules and the routing scripts. They carry over.
- Mount the modules on standoffs inside the enclosure, keep the HV wires short, and give each module its own fuse.
## 8. Findings
| ID | Priority | Finding | Action |
|---|---|---|---|
| M1 | **Must** | The panel make, power and data pinouts are unknown. J2 follows the Stern CN2 order. | Photograph the label and connectors and map every pin. Wrong wiring can destroy the panel. |
| M2 | **Must** | The watchdog gate drive is marginal (6.1). | Drive the charge pump from the 74HCT245 (5 V), or use an NPN transistor. |
| M3 | **Must** | The TVS clamp levels depend on the panel's maximum voltages. | Choose them once the panel is identified. Clamp above the setpoint, below the panel maximum. |
| M4 | **Must** | Counterfeit NCH6100HV modules are a fire hazard. | Buy from an authorized seller. |
| S1 | Should | +5 V has no fuse or clamp. | Add a PTC of about 1.5 A and an SMBJ5.0A. |
| S2 | Should | Shared ground with USB. | No USB while HV is on, or use a USB isolator. |
| S3 | Should | Exposed HV on the modules and the panel. | Tool-only, vented enclosure with HV labels. |
| S4 | Should | The B1212S is unregulated. | Fit a minimum-load resistor and a 15 V zener, or use a regulated isolated module. |
| S5 | Should | Module quality varies (burst mode, no-load overshoot). | Bench-test each module on a dummy load before connecting the panel. |
| S6 | Should | The 6 W isolated converter limits −112 V to about 40–45 mA. | Measure the panel current. If it's higher, use a 10 W converter. |
| N1 | Nice | 1 s watchdog drop-out. | C4 = 1 µF for about 0.1 s. |
| N2 | Nice | GPIO16/17 on WROVER modules. | Check the module type. |
| N3 | Nice | Scan jitter with Wi-Fi. | Scan from a high-priority task or I2S/DMA. |
| N4 | Nice | RC filter values are a guess. | Tune after measuring with a scope. |
| N5 | Nice | Extra margin on the HV side. | Conformal coating. |
## 9. Cost estimate (rev 0.2)
Prices are rough, as of October 2026, in euros including VAT, for a single unit. AliExpress items vary.
### Modules and power
| Item | Qty | Estimate |
|---|---|---|
| 12 V 3 A brick, certified (Mean Well GST36E12 / GST36B12) | 1 | €17–25 |
| Mini560 5 V buck | 1 | €1–2 |
| 45–390 V adjustable boost module (+65 V) | 1 | €6–12 |
| NCH6100HV, **genuine** (−112 V) | 1 | €8–20 |
| Isolated DC-DC 12→12 V, 6 W, regulated (Mornsun YMD-6WR3 class) | 1 | €7–10 |
| B1212S-1W isolated (−100 V offset) | 1 | €1–3 (€4–6 for a regulated type) |
| Relay module, 5 V, opto | 1 | €1–2 |
| Wemos D1 mini ESP32 | 1 | €4–8 |
| **Subtotal** | | **€45–84** |
### Board parts (Mouser / Farnell / TME / LCSC)
| Item | Estimate |
|---|---|
| 5 × 5×20 mm fuse holder + fuses (3 A T, 2 × 1 A T, 2 × 100 mA F) + spares | €5–8 |
| TVS: P6KE15A, SMBJ5.0A, HV clamps × 2, 15 V zener | €2–3 |
| PTC 1.5 A | €0.5 |
| RC filters: 47 Ω 1 W + 22 µF/250 V, × 2–3 | €3–4 |
| Bleeders, minimum load, 2 LEDs | €1–2 |
| Watchdog parts (diodes, caps, transistor, resistors) | €1 |
| 74HCT245 + socket + decoupling | €1–2 |
| Terminal blocks × 7–8 (clone vs Phoenix) | €4–10 |
| Molex KK-396 8-pin header + housing + crimps | €2–4 |
| Header, jumper, standoffs, screws | €2 |
| **Subtotal** | **€22–37** |
### Build
| Item | Estimate |
|---|---|
| PCB, 5 pcs, 2-layer, about 100 × 80 mm (JLCPCB/PCBWay) + shipping | €10–25 |
| Vented plastic enclosure | €8–15 |
| Wire, ferrules, heat-shrink, 14-pin IDC ribbon for the panel data | €5–10 |
| Shipping / small-order fees (distributors, AliExpress) | €0–20 |
| **Subtotal** | **€23–70** |
### Total
| | Low | Typical | High |
|---|---|---|---|
| **rev 0.2, module-only** | **≈ €90** | **≈ €120** | **≈ €190** |
| rev 0.1, linear (for comparison) | +€15–25 for transistors, zeners, 3 heatsinks, 5 W resistor, plus about €5–15 for the larger PCB | | |
| Original route (replacement DMD PSU board) | €50–170 for the board, **plus** an AC transformer and mains wiring | | |
## 10. Next steps
1. Identify the panel (M1). That unblocks M3 and the J2 pinout.
2. Order the modules (genuine NCH6100HV, M4). Bench-test each one on a dummy load (S5).
3. Update the schematic, PCB, BOM and docs to rev 0.2, including M2, S1 and S4.
4. First power-up: series resistors and current measurements (S6). Then tune the RC filters (N4).
## Sources
- NCH6100HV price and counterfeit warning: [eBay listing](https://www.ebay.de/itm/296810265488), [neonixie-l (designer)](https://groups.google.com/g/neonixie-l/c/e8nm73UTEyk/m/e-skGAfqBAAJ), [Elecrow NCH8200HV](https://www.elecrow.com/nch8200hv-nixie-dc-dc-high-voltage-boost-power-converter.html)
- 45–390 V boost module: [AR5388 datasheet](https://uelectronics.com/wp-content/uploads/2026/07/AR5388-Convertidor-Boost-8-32V-a-45-390V-5A-Datasheet.pdf), [Sigmanortec](https://sigmanortec.ro/en/voltage-booster-module-zvs-dc-dc-45-390v-adjustable)
- B1212S-1W: [DigiKey B1212S-1WR3](https://digikey.cn/en/products/detail/mornsun-america-llc/B1212S-1WR3/13968708), [Robu](https://st.robu.in/product/b1212s-1wr3-dc-power-supply-module-1d1212-dc-dc-isolation-module-12v-to-12v1w-isolation-module)
- Mornsun 6 W isolated: [smbom URA2412YMD-6WR3](https://www.smbom.com/product/ura2412ymd-6wr3-3774098), [YMD-6WR3 datasheet](https://cdn.shopify.com/s/files/1/1180/9692/files/URA_YMD-6WR3.pdf)
- Mean Well GST36: [Farnell GST36E12](https://ie.farnell.com/mean-well/gst36e12-p1j/adaptor-ac-dc-12v-3a/dp/2815899), [RS GST36B12](https://de.rs-online.com/web/p/steckernetzteile/1176146)
- Rail voltages: [PinWiki Williams WPC](https://www.pinwiki.com/wiki/index.php?title=Williams_WPC)
+48 -5
View File
@@ -29,6 +29,9 @@ Wire the isolated DC-DC output straight to NCH6100HV #2's input, off-board. Both
| `check_netlist.py` | Exports the netlist with kicad-cli and checks every pin against `PARTS` | | `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.kicad_sch` | Generated schematic (labels-per-pin style: connectivity is by net label) |
| `plasma_dmd_psu.pdf` | Plot of the schematic | | `plasma_dmd_psu.pdf` | Plot of the schematic |
| `plasma_dmd.pretty/`, `fp-lib-table` | Project footprint library (courtyard-less heatsink) |
| `layout_pcb.py`, `route_pcb.py` | Placement and autorouting scripts (see below) |
| `plasma_dmd_psu.kicad_dru` | Custom clearance rules |
```bash ```bash
python hardware/kicad/gen_kicad.py python hardware/kicad/gen_kicad.py
@@ -43,7 +46,8 @@ stop using the generator once you start editing the schematic in KiCad.
| Class | Nets | Clearance | Track | | Class | Nets | Clearance | Track |
|---|---|---|---| |---|---|---|---|
| HV | +90V_RAW, +65V, −130V_RAW, −112V, −100V, HVP/HVN_MOD and all regulator internals | 1.3 mm (3.0 mm to LV via custom rules) | 0.6 mm | | HV | +90V_RAW, +65V, −130V_RAW, −112V, −100V, HVP/HVN_MOD and all regulator internals | 1.3 mm (3.0 mm to LV via custom rules) | 0.6 mm |
| Power | GND, +12V*, +5V, +12V_PANEL | 0.3 mm | 1.0 mm | | Power3A | +12V_IN, +12V (behind the 3 A fuse F1) | 0.3 mm | 1.5 mm |
| Power | GND (plus the bottom pour), +12V_HV/HVP/HVN, +5V, +12V_PANEL | 0.3 mm | 1.0 mm |
| Default | Watchdog signals, LED cathode/anode nodes | 0.25 mm | 0.4 mm | | Default | Watchdog signals, LED cathode/anode nodes | 0.25 mm | 0.4 mm |
## PCB (rev 0.1) ## PCB (rev 0.1)
@@ -58,10 +62,49 @@ DRC is clean: 0 violations, 0 unconnected pads, schematic parity OK.
A dashed silkscreen line marks the boundary, with DANGER text on both sides. A dashed silkscreen line marks the boundary, with DANGER text on both sides.
- **J2 (panel)** straddles the boundary. Its LV pins (GND, +5V, +12V) leave towards the left, its HV pins towards the right. - **J2 (panel)** straddles the boundary. Its LV pins (GND, +5V, +12V) leave towards the left, its HV pins towards the right.
- **Terminal blocks** sit on the top and bottom edges, with wire entries facing outwards. - **Terminal blocks** sit on the top and bottom edges, with wire entries facing outwards.
- **Heatsink space** is left above Q3, Q5 and Q6. - **Heatsinks** HS1–HS3 (Aavid 576802B03900G clip-on, about 20 K/W) sit on Q3, Q5 and Q6.
> ⚠️ **The TO-220 tabs are live.** Q3's tab is at +90 V, Q5's at −130 V and Q6's at Q6_C. > ⚠️ **The heatsinks are live.** Each one clips straight onto its transistor's tab: HS1 is at +90 V,
> Give each transistor its own heatsink, keep the heatsinks apart, and don't let them touch anything. > HS2 at −130 V and HS3 at Q6_C. That's why each heatsink's solder pin is on the collector net,
> so the HV clearance rules also protect it. Never let the heatsinks touch each other or anything else.
### Heatsinks
- **Footprint:** `plasma_dmd:Heatsink_AAVID_576802B03900G_NoCourtyard` in the project library
`plasma_dmd.pretty`. It's a copy of KiCad's footprint with the courtyard and silkscreen outline
removed, because the clip wraps around the transistor and would always overlap its courtyard.
The outline is kept on F.Fab, and the footprint is marked "exempt from courtyard requirement".
- **Placement:** `layout_pcb.py` places each heatsink relative to its transistor (`heatsink_for()`),
with its tab face on the TO-220 tab.
- **Dissipation (estimate):** about 1–1.5 W per transistor. Bare TO-220 runs at about 62 K/W
(60–90 °C rise); with the heatsink, about 20 K/W (about 30 °C rise).
- **Project setting:** "PTH inside courtyard" is set to ignore, because each heatsink pin sits just
behind its tab, inside the transistor courtyard. Courtyard *overlaps* are still checked for all
other parts.
- **Remaining DRC items:** the 9 silkscreen warnings are the TO-220 outlines over the heatsink pins.
The fab clips silkscreen off pads, so they're harmless.
### Current capacity
Track widths follow IPC-2221 for 1 oz outer copper. Currents are estimated from the fuses.
| Net | Max current (fuse) | Width | Capacity at 10 °C rise |
|---|---|---|---|
| +12V_IN, +12V | 3 A (F1) | 1.5 mm | ≈ 3.2 A |
| GND return | 3 A | 1.0 mm + solid bottom pour on the LV side | well above 3 A |
| +12V_HV | 2 A (F2 + F3) | 1.0 mm | ≈ 2.4 A |
| +12V_HVP, +12V_HVN | 1 A (F2, F3) | 1.0 mm | ≈ 2.4 A |
| +5V | about 1 A (unfused, from the Mini560) | 1.0 mm | ≈ 2.4 A |
| HV nets | 100 mA (F4, F5) | 0.6 mm | ≈ 1.7 A (width set by clearance, not current) |
| Signals | mA | 0.4 mm | ≈ 1.2 A |
The 12 V input path, GND and +5V have no vias.
- **Note on F3:** if the negative rail ever drew the full 100 mA that F5 allows (about 13 W), the
isolated converter plus the boost module would pull more than 1 A at 12 V. That would blow F3.
Measure the real HV current at bring-up.
- **Note on +5V:** the 5 V side isn't fused on its own. Consider a polyfuse (about 1.5 A) if the panel
or the ESP32 wiring could short.
### Clearance rules ### Clearance rules
@@ -98,7 +141,7 @@ Once you start hand-editing the board in KiCad, stop running these scripts. They
- [ ] Check every footprint against the parts you actually buy: fuse holders, the 12.5 mm caps, the R13 5 W size, and the terminal block pitch. - [ ] Check every footprint against the parts you actually buy: fuse holders, the 12.5 mm caps, the R13 5 W size, and the terminal block pitch.
- [ ] Map J2 to your panel's power connector. The current order follows the Stern CN2 connector. - [ ] Map J2 to your panel's power connector. The current order follows the Stern CN2 connector.
- [ ] Check heatsink size and clearance for Q3/Q5/Q6. - [ ] Check the heatsink orientation against the Aavid 5768 drawing. KiCad has no 3D model for it, so it isn't visible in the render.
- [ ] Consider conformal coating on the HV side. - [ ] Consider conformal coating on the HV side.
- [ ] Get a second pair of eyes on the HV layout. - [ ] Get a second pair of eyes on the HV layout.
+4
View File
@@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "plasma_dmd")(type "KiCad")(uri "${KIPRJMOD}/plasma_dmd.pretty")(options "")(descr "Project footprints"))
)
+16 -2
View File
@@ -35,6 +35,8 @@ FP_CP125 = "Capacitor_THT:CP_Radial_D12.5mm_P5.00mm"
FP_HDR2 = "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical" 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_KK8 = "Connector_Molex:Molex_KK-396_A-41791-0008_1x08_P3.96mm_Vertical"
FP_HOLE = "MountingHole:MountingHole_3.2mm_M3" FP_HOLE = "MountingHole:MountingHole_3.2mm_M3"
# Clip-on TO-220 heatsink, ~20 K/W, solder pin. The tab is live, so the pin sits on the collector net.
FP_HS = "plasma_dmd:Heatsink_AAVID_576802B03900G_NoCourtyard" # project lib, see plasma_dmd.pretty
def tb(n): def tb(n):
@@ -79,6 +81,7 @@ PARTS = [
("D5", "Device:D_Zener", "1N4760A 68V", FP_D41, {"K": "Q3_B", "A": "GND"}, 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), ("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), ("C6", "Device:C", "0.1uF 500V", FP_CFILM, {"1": "+65V", "2": "GND"}, 2),
("HS1", "Mechanical:Heatsink_Pad", "Aavid 576802B03900G (Q3, live +90V)", FP_HS, {"1": "+90V_RAW"}, 2),
("R6", "Device:R", "330k", FP_R, {"1": "+65V", "2": "GND"}, 2), ("R6", "Device:R", "330k", FP_R, {"1": "+65V", "2": "GND"}, 2),
# 4. Negative HV # 4. Negative HV
("J6", "Connector:Screw_Terminal_01x02", "Isolated DC-DC in: VIN GND", tb(2), ("J6", "Connector:Screw_Terminal_01x02", "Isolated DC-DC in: VIN GND", tb(2),
@@ -103,6 +106,8 @@ PARTS = [
("Q6", "Transistor_BJT:Q_NPN_BCE", "MJE15030", FP_TO220, {"B": "Q6_B", "C": "Q6_C", "E": "-100V"}, 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), ("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), ("C9", "Device:C", "0.1uF 500V", FP_CFILM, {"1": "GND", "2": "-100V"}, 3),
("HS2", "Mechanical:Heatsink_Pad", "Aavid 576802B03900G (Q5, live -130V)", FP_HS, {"1": "-130V_RAW"}, 3),
("HS3", "Mechanical:Heatsink_Pad", "Aavid 576802B03900G (Q6, live Q6_C)", FP_HS, {"1": "Q6_C"}, 3),
# 5. Panel connector (Stern 520-5138 CN2 order; verify against your panel!) # 5. Panel connector (Stern 520-5138 CN2 order; verify against your panel!)
("J2", "Connector_Generic:Conn_01x08", "Panel power (Stern CN2 order)", FP_KK8, ("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), {"1": "-112V", "2": "-100V", "3": None, "4": "GND", "5": "GND", "6": "+5V", "7": "+12V_PANEL", "8": "+65V"}, 4),
@@ -216,7 +221,10 @@ def snap(v):
# N_LED1/N_LED2 sit a LED drop away from GND, so they stay in Default. # N_LED1/N_LED2 sit a LED drop away from GND, so they stay in Default.
HV_NETS = ["+90V_RAW", "+65V", "HVP_MOD", "Q2_E", "REF_P", "Q3_B", HV_NETS = ["+90V_RAW", "+65V", "HVP_MOD", "Q2_E", "REF_P", "Q3_B",
"-130V_RAW", "-112V", "-100V", "HVN_MOD", "Q4_E", "REF_N", "Q5_B", "REF_M", "Q6_B", "Q6_C"] "-130V_RAW", "-112V", "-100V", "HVN_MOD", "Q4_E", "REF_N", "Q5_B", "REF_M", "Q6_B", "Q6_C"]
POWER_NETS = ["GND", "+12V", "+12V_IN", "+12V_HV", "+12V_HVP", "+12V_HVN", "+5V", "+12V_PANEL"] # Track widths from IPC-2221 (1 oz outer copper): 1.5 mm carries ~3.2 A at 10 C rise, 1.0 mm ~2.4 A.
POWER3A_NETS = ["+12V", "+12V_IN"] # behind F1 (3 A)
# GND: 1.0 mm tracks plus the bottom-layer pour, which carries the 3 A return on the LV side
POWER_NETS = ["GND", "+12V_HV", "+12V_HVP", "+12V_HVN", "+5V", "+12V_PANEL"] # <= 2 A
def netclass(name, clearance, track, priority): def netclass(name, clearance, track, priority):
@@ -236,15 +244,21 @@ def write_project():
data["net_settings"] = { data["net_settings"] = {
"classes": [ "classes": [
netclass("Default", 0.25, 0.4, 2147483647), netclass("Default", 0.25, 0.4, 2147483647),
netclass("Power", 0.3, 1.0, 1), netclass("Power3A", 0.3, 1.5, 1),
netclass("Power", 0.3, 1.0, 2),
netclass("HV", 1.3, 0.6, 0) # HV <-> LV 3 mm comes from plasma_dmd_psu.kicad_dru, netclass("HV", 1.3, 0.6, 0) # HV <-> LV 3 mm comes from plasma_dmd_psu.kicad_dru,
], ],
"meta": {"version": 5}, "meta": {"version": 5},
"net_colors": None, "net_colors": None,
"netclass_assignments": None, "netclass_assignments": None,
"netclass_patterns": [{"netclass": "HV", "pattern": "/" + n} for n in HV_NETS] "netclass_patterns": [{"netclass": "HV", "pattern": "/" + n} for n in HV_NETS]
+ [{"netclass": "Power3A", "pattern": "/" + n} for n in POWER3A_NETS]
+ [{"netclass": "Power", "pattern": "/" + n} for n in POWER_NETS], + [{"netclass": "Power", "pattern": "/" + n} for n in POWER_NETS],
} }
# The heatsink pins sit just behind the TO-220 tab, inside the transistor courtyard. The heatsinks
# are the only courtyard-less parts, so this check adds nothing beyond courtyards_overlap.
sev = data.setdefault("board", {}).setdefault("design_settings", {}).setdefault("rule_severities", {})
sev["pth_inside_courtyard"] = "ignore"
pro.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") pro.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+69 -19
View File
@@ -60,22 +60,23 @@ PLACE = {
"Q5": (81.0, 70.0, 0), # heatsink space above (tab side) "Q5": (81.0, 70.0, 0), # heatsink space above (tab side)
"Q6": (99.0, 70.0, 0), "Q6": (99.0, 70.0, 0),
"R13": (114.0, 62.0, 0), # 5 W, runs warm: keep clear of caps "R13": (114.0, 62.0, 0), # 5 W, runs warm: keep clear of caps
"D7": (74.0, 80.5, 0), "D7": (74.0, 83.5, 0),
"D8": (90.0, 79.0, 0), "D8": (90.0, 83.0, 0),
"R12": (106.0, 79.0, 0), "R12": (106.0, 83.0, 0),
"C8": (122.0, 79.0, 0), "C8": (122.0, 83.0, 0),
"D6": (68.0, 89.0, 0), "D6": (68.0, 93.0, 0),
"R10": (82.0, 89.0, 0), "R10": (82.0, 93.0, 0),
"R9": (105.0, 89.0, 0), "R9": (105.0, 93.0, 0),
"Q4": (120.0, 90.0, 0), "Q4": (120.0, 94.0, 0),
"R11": (130.0, 89.0, 0), "R11": (130.0, 93.0, 0),
"C9": (68.0, 99.0, 0), "C9": (68.0, 103.0, 0),
"C7": (92.0, 101.0, 0), "C7": (92.0, 105.0, 0),
"R7": (106.0, 99.0, 0), "R7": (106.0, 103.0, 0),
"R8": (122.0, 99.0, 0), "R8": (122.0, 103.0, 0),
"LED2": (144.0, 99.0, 0), "LED2": (144.0, 103.0, 0),
"F5": (120.0, 111.0, 0), "F5": (120.0, 113.5, 0),
"J7": (70.0, 118.5, 0), # NCH6100HV #2 output "J7": (70.0, 118.5, 0), # NCH6100HV #2 output
# ---- heatsinks: see heatsink_for() ------------------------------------------------
# ---- mounting holes ------------------------------------------------------------ # ---- mounting holes ------------------------------------------------------------
"H1": (4.0, 4.0, 0), "H1": (4.0, 4.0, 0),
"H2": (W - 4.0, 4.0, 0), "H2": (W - 4.0, 4.0, 0),
@@ -83,13 +84,26 @@ PLACE = {
"H4": (W - 4.0, H - 4.0, 0), "H4": (W - 4.0, H - 4.0, 0),
} }
HEATSINKS = {"HS1": "Q3", "HS2": "Q5", "HS3": "Q6"}
def heatsink_for(q):
"""Aavid 576802B03900G on a TO-220-3_Vertical at rotation 0 (tab towards -y)."""
x, y, _ = PLACE[q]
return (x + 2.54, y - 3.6, 270)
for _hs, _q in HEATSINKS.items():
PLACE[_hs] = heatsink_for(_q)
SILK = [ SILK = [
# (text, x, y, size, layer) # (text, x, y, size, layer)
("PLASMA DMD PSU rev 0.1", 24.0, 102.0, 1.5, "F.SilkS"), ("PLASMA DMD PSU rev 0.1", 24.0, 102.0, 1.5, "F.SilkS"),
("J1 12V IN", 13.5, 13.6, 1.0, "F.SilkS"), ("J1 12V IN", 13.5, 13.6, 1.0, "F.SilkS"),
("J3 MINI560", 31.6, 13.6, 1.0, "F.SilkS"), ("J3 MINI560", 31.6, 13.6, 1.0, "F.SilkS"),
("J8 ESP32", 52.1, 13.6, 1.0, "F.SilkS"), ("J8 ESP32", 52.1, 13.6, 1.0, "F.SilkS"),
("J5 HV MOD 1", 71.1, 13.6, 1.0, "F.SilkS"), ("J5 HV MOD 1", 68.0, 13.6, 1.0, "F.SilkS"),
("J4 RELAY MODULE", 21.2, 111.2, 1.0, "F.SilkS"), ("J4 RELAY MODULE", 21.2, 111.2, 1.0, "F.SilkS"),
("J6 ISO DC-DC IN", 41.5, 111.2, 1.0, "F.SilkS"), ("J6 ISO DC-DC IN", 41.5, 111.2, 1.0, "F.SilkS"),
("J7 HV MOD 2 OUT", 72.5, 111.2, 1.0, "F.SilkS"), ("J7 HV MOD 2 OUT", 72.5, 111.2, 1.0, "F.SilkS"),
@@ -109,6 +123,12 @@ def pt(x, y):
return pcbnew.VECTOR2I(mm(OX + x), mm(OY + y)) return pcbnew.VECTOR2I(mm(OX + x), mm(OY + y))
def lib_dir(lib):
"""Project library (plasma_dmd.pretty next to the board) or a KiCad standard library."""
local = HERE / f"{lib}.pretty"
return local if local.exists() else FP_DIR / f"{lib}.pretty"
FP_DIR = Path(pcbnew.__file__).parents[3] / "share/kicad/footprints" # <KiCad>/10.0/share/... FP_DIR = Path(pcbnew.__file__).parents[3] / "share/kicad/footprints" # <KiCad>/10.0/share/...
@@ -131,7 +151,7 @@ def sync_footprints(board):
if not fpid or fpid == old.GetFPIDAsString(): if not fpid or fpid == old.GetFPIDAsString():
continue continue
lib, name = fpid.split(":") lib, name = fpid.split(":")
new = pcbnew.FootprintLoad(str(FP_DIR / f"{lib}.pretty"), name) new = pcbnew.FootprintLoad(str(lib_dir(lib)), name)
new.SetFPID(pcbnew.LIB_ID(lib, name)) new.SetFPID(pcbnew.LIB_ID(lib, name))
new.SetReference(ref) new.SetReference(ref)
new.SetValue(old.GetValue()) new.SetValue(old.GetValue())
@@ -148,8 +168,33 @@ def sync_footprints(board):
print("swapped", ref, "->", fpid) print("swapped", ref, "->", fpid)
def add_missing_footprints(board):
"""Add footprints for schematic symbols that are not on the board yet (like Update PCB, F8)."""
import gen_kicad
_, uuids = gen_kicad.existing_uuids()
present = {f.GetReference() for f in board.GetFootprints()}
for ref, lib_id, value, fpid, conns, _g in gen_kicad.PARTS:
if ref in present:
continue
lib, name = fpid.split(":")
fp = pcbnew.FootprintLoad(str(lib_dir(lib)), name)
fp.SetFPID(pcbnew.LIB_ID(lib, name))
fp.SetReference(ref)
fp.SetValue(value)
fp.SetPath(pcbnew.KIID_PATH("/" + uuids[ref]))
fp.SetSheetname("/")
fp.SetSheetfile("plasma_dmd_psu.kicad_sch")
board.Add(fp)
for pad in fp.Pads():
net = conns.get(pad.GetNumber())
if net:
pad.SetNet(board.FindNet("/" + net))
print("added", ref, "->", fpid)
def main(): def main():
board = pcbnew.LoadBoard(str(BOARD)) board = pcbnew.LoadBoard(str(BOARD))
add_missing_footprints(board)
sync_footprints(board) sync_footprints(board)
# clean previous layout output (outline, our silkscreen, tracks, vias, zones) # clean previous layout output (outline, our silkscreen, tracks, vias, zones)
@@ -175,9 +220,14 @@ def main():
f.Flip(f.GetPosition(), False) f.Flip(f.GetPosition(), False)
f.SetOrientationDegrees(rot) f.SetOrientationDegrees(rot)
f.SetPosition(pt(x, y)) f.SetPosition(pt(x, y))
f.SetLocked(ref.startswith(("J", "H"))) fixed = ref.startswith("J") or (ref.startswith("H") and ref[1:].isdigit())
if ref.startswith(("J", "H")): f.SetLocked(fixed)
if fixed:
f.Reference().SetVisible(False) # connectors are labelled by the SILK texts f.Reference().SetVisible(False) # connectors are labelled by the SILK texts
if ref in HEATSINKS.values():
f.Reference().SetPosition(pt(x + 2.54, y + 3.2)) # below the pins, clear of the heatsink pin
if ref in HEATSINKS:
f.Reference().SetPosition(pt(x + 9.5, y + 2.0)) # beside the heatsink
# board outline with 2 mm corner radius # board outline with 2 mm corner radius
r = 2.0 r = 2.0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 KiB

After

Width:  |  Height:  |  Size: 950 KiB

@@ -0,0 +1,219 @@
(footprint "Heatsink_AAVID_576802B03900G_NoCourtyard"
(version 20260206)
(generator "pcbnew")
(generator_version "10.0")
(layer "F.Cu")
(descr "Courtyard and silkscreen outline removed (outline kept on F.Fab): the clip wraps around its TO-220, so its courtyard would always overlap it. Heatsink, 14.48x12.7x19.05mm, TO-220/ TO-262, https://www.boydcorp.com/aavid-datasheets/Board-Level-Cooling-Plug-In-5768.pdf")
(tags "heatsink")
(property "Reference" "REF**"
(at 6.35 -8.5 0)
(layer "F.SilkS")
(uuid "07f046b7-ee46-436d-a8d8-54e735bd514a")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "Heatsink_AAVID_576802B03900G"
(at 6.35 8.5 0)
(layer "F.Fab")
(uuid "0c915f93-8a39-4587-9dc9-356a06b61836")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "715aca18-8f8d-4d48-a4e1-5c8463d0d1a0")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "3dce55db-d6b0-43d7-b0f4-0d716ceb00b9")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr through_hole allow_missing_courtyard)
(duplicate_pad_numbers_are_jumpers no)
(fp_line
(start 0.405 -1.85)
(end 1.675 -1.85)
(stroke
(width 0.1)
(type solid)
)
(layer "Dwgs.User")
(uuid "e2130da5-6bfd-4cff-a6e0-ce36fa9ed320")
)
(fp_line
(start 0.405 1.85)
(end 1.675 1.85)
(stroke
(width 0.1)
(type solid)
)
(layer "Dwgs.User")
(uuid "fad026d3-0846-44b8-ac9b-fc5c71aeb7d2")
)
(fp_line
(start 0.45 -5)
(end 4.45 -5)
(stroke
(width 0.1)
(type solid)
)
(layer "Dwgs.User")
(uuid "684b317f-96b2-4563-94b7-a9f9dbce19d3")
)
(fp_line
(start 1.675 -5)
(end 1.675 5)
(stroke
(width 0.1)
(type solid)
)
(layer "Dwgs.User")
(uuid "e1c3e282-b6b0-4028-8ab4-94f132389afb")
)
(fp_line
(start 4.45 -5)
(end 4.45 5)
(stroke
(width 0.1)
(type solid)
)
(layer "Dwgs.User")
(uuid "2be7b066-c2ff-480b-85f2-398b9dc58fb3")
)
(fp_line
(start 4.45 5)
(end 0.45 5)
(stroke
(width 0.1)
(type solid)
)
(layer "Dwgs.User")
(uuid "f5f503b5-f93a-4eb0-a9b7-af9692dd025f")
)
(fp_line
(start -0.405 -6.35)
(end -0.405 6.35)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "dd94d98b-8847-4ce0-b30f-d31c6f77bc25")
)
(fp_line
(start -0.405 -6.35)
(end 12.295 -7.24)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "b22575e4-0838-4d53-a202-9782ea057b6e")
)
(fp_line
(start 0.405 -5.597)
(end 0.405 5.597)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "a9099b8e-83d3-42a9-8fc6-6ccc4acfec5a")
)
(fp_line
(start 0.405 -5.597)
(end 12.295 -6.43)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "872fc3e3-6f14-4f00-aac2-46697acceeb4")
)
(fp_line
(start 12.295 -7.24)
(end 12.295 7.24)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "8f66f8ce-b782-479c-bb13-b0f17be7fa48")
)
(fp_line
(start 12.295 7.24)
(end -0.405 6.35)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "8bb6ea79-3367-4e22-b8d7-6f275fda6f15")
)
(fp_line
(start 12.3 6.43)
(end 0.405 5.597)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "ec19e519-279b-4d08-8288-be58fe8a8d44")
)
(fp_text user "${REFERENCE}"
(at 6.35 0 0)
(layer "F.Fab")
(uuid "6ecf39c3-7bd4-4dc2-b3d8-015e1ce8efe0")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole oval
(at 0 0)
(size 2.6 3.78)
(drill oval 1.1 2.28)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "ec39e68d-6144-43b8-abb3-6034313acdbd")
)
(embedded_fonts no)
(model "${KICAD10_3DMODEL_DIR}/Heatsink.3dshapes/Heatsink_AAVID_576802B03900G.step"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)
File diff suppressed because it is too large. Load diff
+27 -8
View File
@@ -112,7 +112,7 @@
"nonmirrored_text_on_back_layer": "warning", "nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error", "npth_inside_courtyard": "error",
"padstack": "warning", "padstack": "warning",
"pth_inside_courtyard": "error", "pth_inside_courtyard": "ignore",
"shorting_items": "error", "shorting_items": "error",
"silk_edge_clearance": "warning", "silk_edge_clearance": "warning",
"silk_over_copper": "warning", "silk_over_copper": "warning",
@@ -542,13 +542,32 @@
"microvia_drill": 0.1, "microvia_drill": 0.1,
"name": "Power", "name": "Power",
"pcb_color": "rgba(0, 0, 0, 0.000)", "pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 1, "priority": 2,
"schematic_color": "rgba(0, 0, 0, 0.000)", "schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 1.0, "track_width": 1.0,
"tuning_profile": "", "tuning_profile": "",
"via_diameter": 1.0, "via_diameter": 1.0,
"via_drill": 0.5, "via_drill": 0.5,
"wire_width": 6 "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": "Power3A",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 1.5,
"tuning_profile": "",
"via_diameter": 1.0,
"via_drill": 0.5,
"wire_width": 6
} }
], ],
"meta": { "meta": {
@@ -622,17 +641,17 @@
"pattern": "/Q6_C" "pattern": "/Q6_C"
}, },
{ {
"netclass": "Power", "netclass": "Power3A",
"pattern": "/GND"
},
{
"netclass": "Power",
"pattern": "/+12V" "pattern": "/+12V"
}, },
{ {
"netclass": "Power", "netclass": "Power3A",
"pattern": "/+12V_IN" "pattern": "/+12V_IN"
}, },
{
"netclass": "Power",
"pattern": "/GND"
},
{ {
"netclass": "Power", "netclass": "Power",
"pattern": "/+12V_HV" "pattern": "/+12V_HV"
File diff suppressed because it is too large. Load diff
Binary file not shown.
+53 -2
View File
@@ -26,7 +26,8 @@ PASSES = int(sys.argv[1]) if len(sys.argv) > 1 else 100
# (routing clearance + half the width difference) from its neighbours. This gets 3 mm HV <-> LV # (routing clearance + half the width difference) from its neighbours. This gets 3 mm HV <-> LV
# and 1.3 mm HV <-> HV while tracks can still squeeze out between transistor pins. # and 1.3 mm HV <-> HV while tracks can still squeeze out between transistor pins.
# class: (routing width um, routing clearance um, final width mm) # class: (routing width um, routing clearance um, final width mm)
ROUTE = {"kicad_default": (1800, 250, 0.4), "Power": (2400, 300, 1.0), "HV": (2100, 650, 0.6)} ROUTE = {"kicad_default": (1800, 250, 0.4), "Power": (2400, 300, 1.0), "Power3A": (2900, 300, 1.5),
"HV": (2100, 650, 0.6)}
VIA_ROUTE_UM, VIA_FINAL_MM, VIA_DRILL_MM = 2300, 1.0, 0.5 # same trick for vias VIA_ROUTE_UM, VIA_FINAL_MM, VIA_DRILL_MM = 2300, 1.0, 0.5 # same trick for vias
HV_TO_LV_UM = 2450 # + (2.1 - 0.6) / 2 after shrinking = 3.2 mm HV_TO_LV_UM = 2450 # + (2.1 - 0.6) / 2 after shrinking = 3.2 mm
@@ -62,7 +63,7 @@ def add_connector_stubs(board, ref="J2", length=5.0):
""" """
fp = board.FindFootprintByReference(ref) fp = board.FindFootprintByReference(ref)
# final widths: a router track must end on the stub copper itself, which still holds after shrinking # final widths: a router track must end on the stub copper itself, which still holds after shrinking
widths = {"HV": ROUTE["HV"][2], "Power": ROUTE["Power"][2]} widths = {k: v[2] for k, v in ROUTE.items()}
for pad in fp.Pads(): for pad in fp.Pads():
net = pad.GetNet() net = pad.GetNet()
name = net.GetNetname() if net else "" name = net.GetNetname() if net else ""
@@ -81,6 +82,55 @@ def add_connector_stubs(board, ref="J2", length=5.0):
board.Add(t) board.Add(t)
def _closest_on_segment(p, a, b):
ax, ay, bx, by = a.x, a.y, b.x, b.y
dx, dy = bx - ax, by - ay
L2 = dx * dx + dy * dy
u = 0.0 if L2 == 0 else max(0.0, min(1.0, ((p.x - ax) * dx + (p.y - ay) * dy) / L2))
cx, cy = ax + u * dx, ay + u * dy
return int(round(cx)), int(round(cy)), ((p.x - cx) ** 2 + (p.y - cy) ** 2) ** 0.5
def heal_t_junctions(board, reach_mm=1.6):
"""After shrinking, a track that joined another track mid-way (a T-junction) can end beside
the narrowed copper. Snap such dangling ends onto the centre line of the same-net track."""
traces = [t for t in board.GetTracks() if t.Type() == pcbnew.PCB_TRACE_T]
vias = [t for t in board.GetTracks() if t.Type() == pcbnew.PCB_VIA_T]
pads = [p for f in board.GetFootprints() for p in f.Pads()]
reach = pcbnew.FromMM(reach_mm)
fixed = 0
for t in traces:
for is_start in (True, False):
p = t.GetStart() if is_start else t.GetEnd()
net, layer = t.GetNetCode(), t.GetLayer()
if any(pd.GetNetCode() == net and pd.IsOnLayer(layer) and pd.HitTest(p) for pd in pads):
continue
if any(v.GetNetCode() == net and v.GetPosition() == p for v in vias):
continue
best, touching = None, False
for o in traces:
if o is t or o.GetNetCode() != net or o.GetLayer() != layer:
continue
if p in (o.GetStart(), o.GetEnd()):
touching = True
break
cx, cy, d = _closest_on_segment(p, o.GetStart(), o.GetEnd())
if d <= o.GetWidth() / 2:
touching = True
break
if d <= reach and (best is None or d < best[2]):
best = (cx, cy, d)
if touching or best is None:
continue
q = pcbnew.VECTOR2I(best[0], best[1])
if is_start:
t.SetStart(q)
else:
t.SetEnd(q)
fixed += 1
return fixed
def unconnected(board): def unconnected(board):
board.BuildConnectivity() board.BuildConnectivity()
return board.GetConnectivity().GetUnconnectedCount(False) return board.GetConnectivity().GetUnconnectedCount(False)
@@ -199,6 +249,7 @@ def main():
if not pcbnew.ImportSpecctraSES(board, str(ses)): if not pcbnew.ImportSpecctraSES(board, str(ses)):
raise SystemExit("SES import failed") raise SystemExit("SES import failed")
shrink_tracks(board) shrink_tracks(board)
print("healed T-junctions:", heal_t_junctions(board))
remove_dangling_stubs(board) remove_dangling_stubs(board)
add_gnd_plane(board) add_gnd_plane(board)
pcbnew.ZONE_FILLER(board).Fill(board.Zones()) pcbnew.ZONE_FILLER(board).Fill(board.Zones())