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:
1 parent
28c666506e
commit
cdf24e0b4b
15 files changed
+5917
-4730
No files matched your search
@@ -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` |
|
||||
| `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.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
|
||||
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 |
|
||||
|---|---|---|---|
|
||||
| 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 |
|
||||
|
||||
## 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.
|
||||
- **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.
|
||||
- **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.
|
||||
> Give each transistor its own heatsink, keep the heatsinks apart, and don't let them touch anything.
|
||||
> ⚠️ **The heatsinks are live.** Each one clips straight onto its transistor's tab: HS1 is at +90 V,
|
||||
> 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
|
||||
|
||||
@@ -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.
|
||||
- [ ] 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.
|
||||
- [ ] Get a second pair of eyes on the HV layout.
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
(fp_lib_table
|
||||
(version 7)
|
||||
(lib (name "plasma_dmd")(type "KiCad")(uri "${KIPRJMOD}/plasma_dmd.pretty")(options "")(descr "Project footprints"))
|
||||
)
|
||||
@@ -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_KK8 = "Connector_Molex:Molex_KK-396_A-41791-0008_1x08_P3.96mm_Vertical"
|
||||
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):
|
||||
@@ -79,6 +81,7 @@ PARTS = [
|
||||
("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),
|
||||
("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),
|
||||
# 4. Negative HV
|
||||
("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),
|
||||
("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),
|
||||
("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!)
|
||||
("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),
|
||||
@@ -216,7 +221,10 @@ def snap(v):
|
||||
# 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",
|
||||
"-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):
|
||||
@@ -236,15 +244,21 @@ def write_project():
|
||||
data["net_settings"] = {
|
||||
"classes": [
|
||||
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,
|
||||
],
|
||||
"meta": {"version": 5},
|
||||
"net_colors": None,
|
||||
"netclass_assignments": None,
|
||||
"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],
|
||||
}
|
||||
# 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")
|
||||
|
||||
|
||||
|
||||
@@ -60,22 +60,23 @@ PLACE = {
|
||||
"Q5": (81.0, 70.0, 0), # heatsink space above (tab side)
|
||||
"Q6": (99.0, 70.0, 0),
|
||||
"R13": (114.0, 62.0, 0), # 5 W, runs warm: keep clear of caps
|
||||
"D7": (74.0, 80.5, 0),
|
||||
"D8": (90.0, 79.0, 0),
|
||||
"R12": (106.0, 79.0, 0),
|
||||
"C8": (122.0, 79.0, 0),
|
||||
"D6": (68.0, 89.0, 0),
|
||||
"R10": (82.0, 89.0, 0),
|
||||
"R9": (105.0, 89.0, 0),
|
||||
"Q4": (120.0, 90.0, 0),
|
||||
"R11": (130.0, 89.0, 0),
|
||||
"C9": (68.0, 99.0, 0),
|
||||
"C7": (92.0, 101.0, 0),
|
||||
"R7": (106.0, 99.0, 0),
|
||||
"R8": (122.0, 99.0, 0),
|
||||
"LED2": (144.0, 99.0, 0),
|
||||
"F5": (120.0, 111.0, 0),
|
||||
"D7": (74.0, 83.5, 0),
|
||||
"D8": (90.0, 83.0, 0),
|
||||
"R12": (106.0, 83.0, 0),
|
||||
"C8": (122.0, 83.0, 0),
|
||||
"D6": (68.0, 93.0, 0),
|
||||
"R10": (82.0, 93.0, 0),
|
||||
"R9": (105.0, 93.0, 0),
|
||||
"Q4": (120.0, 94.0, 0),
|
||||
"R11": (130.0, 93.0, 0),
|
||||
"C9": (68.0, 103.0, 0),
|
||||
"C7": (92.0, 105.0, 0),
|
||||
"R7": (106.0, 103.0, 0),
|
||||
"R8": (122.0, 103.0, 0),
|
||||
"LED2": (144.0, 103.0, 0),
|
||||
"F5": (120.0, 113.5, 0),
|
||||
"J7": (70.0, 118.5, 0), # NCH6100HV #2 output
|
||||
# ---- heatsinks: see heatsink_for() ------------------------------------------------
|
||||
# ---- mounting holes ------------------------------------------------------------
|
||||
"H1": (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),
|
||||
}
|
||||
|
||||
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 = [
|
||||
# (text, x, y, size, layer)
|
||||
("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"),
|
||||
("J3 MINI560", 31.6, 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"),
|
||||
("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"),
|
||||
@@ -109,6 +123,12 @@ def pt(x, 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/...
|
||||
|
||||
|
||||
@@ -131,7 +151,7 @@ def sync_footprints(board):
|
||||
if not fpid or fpid == old.GetFPIDAsString():
|
||||
continue
|
||||
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.SetReference(ref)
|
||||
new.SetValue(old.GetValue())
|
||||
@@ -148,8 +168,33 @@ def sync_footprints(board):
|
||||
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():
|
||||
board = pcbnew.LoadBoard(str(BOARD))
|
||||
add_missing_footprints(board)
|
||||
sync_footprints(board)
|
||||
|
||||
# clean previous layout output (outline, our silkscreen, tracks, vias, zones)
|
||||
@@ -175,9 +220,14 @@ def main():
|
||||
f.Flip(f.GetPosition(), False)
|
||||
f.SetOrientationDegrees(rot)
|
||||
f.SetPosition(pt(x, y))
|
||||
f.SetLocked(ref.startswith(("J", "H")))
|
||||
if ref.startswith(("J", "H")):
|
||||
fixed = ref.startswith("J") or (ref.startswith("H") and ref[1:].isdigit())
|
||||
f.SetLocked(fixed)
|
||||
if fixed:
|
||||
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
|
||||
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)
|
||||
)
|
||||
)
|
||||
)
|
||||
+4517
-4166
File diff suppressed because it is too large.
Load diff
@@ -112,7 +112,7 @@
|
||||
"nonmirrored_text_on_back_layer": "warning",
|
||||
"npth_inside_courtyard": "error",
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "error",
|
||||
"pth_inside_courtyard": "ignore",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
@@ -542,13 +542,32 @@
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Power",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 1,
|
||||
"priority": 2,
|
||||
"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": 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": {
|
||||
@@ -622,17 +641,17 @@
|
||||
"pattern": "/Q6_C"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/GND"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"netclass": "Power3A",
|
||||
"pattern": "/+12V"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"netclass": "Power3A",
|
||||
"pattern": "/+12V_IN"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/GND"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+12V_HV"
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -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
|
||||
# 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)
|
||||
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
|
||||
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)
|
||||
# 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():
|
||||
net = pad.GetNet()
|
||||
name = net.GetNetname() if net else ""
|
||||
@@ -81,6 +82,55 @@ def add_connector_stubs(board, ref="J2", length=5.0):
|
||||
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):
|
||||
board.BuildConnectivity()
|
||||
return board.GetConnectivity().GetUnconnectedCount(False)
|
||||
@@ -199,6 +249,7 @@ def main():
|
||||
if not pcbnew.ImportSpecctraSES(board, str(ses)):
|
||||
raise SystemExit("SES import failed")
|
||||
shrink_tracks(board)
|
||||
print("healed T-junctions:", heal_t_junctions(board))
|
||||
remove_dangling_stubs(board)
|
||||
add_gnd_plane(board)
|
||||
pcbnew.ZONE_FILLER(board).Fill(board.Zones())
|
||||
|
||||
Reference in new issue
Block a user