Controller board: ESP32 + 2x StepStick carrier (v0.4), FluidNC config

- KiCad project generated by pcb/tools (design, route, schematic, fab outputs).
- v0.4 fixes the v0.3 review findings: low-dropout 3.3 V regulator, OR'd 5 V
  supply, polyfuse, USB ESD protection, input clamps, ceramic decoupling at the
  drivers, A4988/DRV8825 jumper, ground stitching. DRC/ERC clean.
- v0.3 (as ordered) kept in pcb/archive/v0.3.
- FluidNC config.yaml for the board; rho hard limit on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ckochandClaude Opus 5.5 committed 2026-11-01 13:40:23 -05:00
1 parent c498882bbb
commit 1400cc8afd
46 files changed
+226470

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
"""Find free spots for parts added in a revision: a spiral search around each part's
preferred position (from design.py), trying 4 rotations, keeping courtyards apart,
inside the board and out of the antenna keep-out. Prints the positions to paste into design.py."""
import math
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
import design as D
from board import build
GAP = 0.25 # min gap between courtyards, mm
KEEPOUT = (18.75, 56.9, 67.25, D.BOARD_H) # ESP32 antenna
EDGE = 0.6
def overlaps(a, b, gap=GAP):
return min(a[2], b[2]) - max(a[0], b[0]) > -gap and min(a[3], b[3]) - max(a[1], b[1]) > -gap
def legal(part, fixed):
for r in part.court_rects():
if r[0] < EDGE or r[1] < EDGE or r[2] > D.BOARD_W - EDGE or r[3] > D.BOARD_H - EDGE:
return False
if overlaps(r, KEEPOUT, 0):
return False
if any(overlaps(r, f) for f in fixed):
return False
return True
def place(refs, parts):
fixed = [r for ref, p in parts.items() if ref not in refs for r in p.court_rects()]
out = {}
for ref in refs:
p = parts[ref]
x0, y0, rot0 = p.x, p.y, p.rot
best = None
for step in range(0, 80): # radius in 0.25 mm steps, up to 20 mm
rad = step * 0.25
n = max(1, int(2 * math.pi * rad / 0.25))
for i in range(n):
a = 2 * math.pi * i / n
x, y = round(x0 + rad * math.cos(a), 2), round(y0 + rad * math.sin(a), 2)
for rot in (rot0, (rot0 + 90) % 360, (rot0 + 180) % 360, (rot0 + 270) % 360):
p.x, p.y, p.rot = x, y, rot
if legal(p, fixed):
best = (x, y, rot, rad)
break
if best:
break
if best:
break
if not best:
print(f'{ref}: no spot within 20 mm')
p.x, p.y, p.rot = x0, y0, rot0
continue
p.x, p.y, p.rot = best[:3]
fixed += p.court_rects()
out[ref] = best
print(f'{ref}: ({best[0]}, {best[1]}) rot {best[2]} moved {best[3]:.2f} mm from preferred')
return out
if __name__ == '__main__':
parts = build()
place(sys.argv[1:], parts)