molecule
AFS_Adom flight computer molecule
Public Unreviewedby ae007
Active fin stabilization flight computer for a sounding rocket, a clean-room AI redesign of the Rice Eclipse AFS board: RP2350B, ICM-45686, KX134-1211, MMC5983MA, MS5611, TMP117, INA219A, 8 servos, 2S LiPo, USB-C; 4-layer Adom molecule, designed end to end by Claude through KiCad and adom-aiflow
main
ae007
Project page: AFS_Adom rev A (Rice Eclipse AFS clean-room AI redesign): highlight reel and narrated process video with…
cbe460e
7d ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
"""Route the AFS_Adom board offline: EP vias, plane stubs for passives, ordered escapes, nets, retry. Writes board + plan."""
import sys, json, math, time
sys.path.insert(0, '/tmp/claude-1001/-home-adom-project/8819fb5f-c2c6-4524-9528-99428cd6b88e/scratchpad/gen')
import pcb as B
from router import *
t0 = time.time()
R = Router()
log = open(B.OUT + '/outputs/route_log.txt', 'w')
def L(*a):
print(*a, flush=True); print(*a, file=log); log.flush()
VBAT_POLY = [(B.BX0, B.BY0), (B.BX1, B.BY0), (B.BX1, 110.0), (126.2, 110.0), (126.2, 150.6), (122.0, 150.6), (122.0, 110.0), (B.BX0, 110.0)]
lx, ly, _ = B.PL['L3']
LX_KEEP = [(lx - 2.6, ly - 1.6), (lx + 0.2, ly - 1.6), (lx + 0.2, ly + 1.6), (lx - 2.6, ly + 1.6)]
def in_3v3(x, y): return (not R.in_poly(x, y, VBAT_POLY)) and (not R.in_poly(x, y, LX_KEEP))
def in_vbat(x, y): return R.in_poly(x, y, VBAT_POLY)
def in_gnd(x, y): return not R.in_poly(x, y, LX_KEEP)
# 1) RP2350 exposed pad thermal vias
ux, uy, _ = B.PL['U1']
epv = [(ux + dx, uy + dy) for dx in (-1.1, 0, 1.1) for dy in (-1.1, 0, 1.1)]
for (x, y) in epv:
R.vias.append(('GND', x, y, VIA_D, VIA_DRILL)); R.add_via_obstacle('GND', x, y, VIA_D)
R.plan.append(dict(net='GND', kind='epvias', segs=[], vias=epv, width=0.3, via=(VIA_D, VIA_DRILL)))
# 2) plane stubs for passives/connectors only (IC pins are connected through their capacitors)
fails = []
for net, fn, via in (('GND', in_gnd, (VIA_D, VIA_DRILL)), ('+3V3', in_3v3, (VIA_D, VIA_DRILL)), ('VBAT', in_vbat, (PVIA_D, PVIA_DRILL))):
pads = [p for p in R.netpads.get(net, []) if not p.tht and not p.ref.startswith('U')]
n_ok = 0
for p in pads:
if R.plane_stub_fn(p, fn, via=via, maxr=4.0 if net != 'VBAT' else 14.0):
n_ok += 1
else:
fails.append((net, p.ref, p.num))
L('%s stubs: %d/%d ok (%.0fs)' % (net, n_ok, len(pads), time.time() - t0))
L('stub failures:', fails)
# 3) ordered escapes from the RP2350 bottom row (outer pins first) and the left-column bottom pins
def pin_net(ref, num):
return B.COMPS[ref]['pads'].get(num)
escape = [('U1', '80'), ('U1', '79'), ('U1', '78'), ('U1', '77'), ('U1', '76'), ('U1', '75'), ('U1', '74'), ('U1', '73'), ('U1', '72'), ('U1', '71'), ('U1', '70'),
('U1', '69'), ('U1', '68'), ('U1', '67'), ('U1', '66'), ('U1', '65'), ('U1', '64'), ('U1', '63'), ('U1', '62'), ('U1', '61'), ('U1', '60'), ('U1', '59')]
routed_nets = set()
for ref, num in escape:
net = pin_net(ref, num)
if net in ('GND', '+3V3'):
ok = R.connect_pad(ref, num)
L('%-5s %-4s %-12s %s' % (ref, num, net, 'ok' if ok else 'FAILED'))
else:
if net in routed_nets:
continue
ok = R.route_net(net)
routed_nets.add(net)
L('%-5s %-4s %-12s %s (net)' % (ref, num, net, 'ok' if ok else 'FAILED'))
def mst_len(net):
pads = R.netpads[net]
if len(pads) < 2:
return 0
rem = pads[1:]; conn = [pads[0]]; tot = 0
while rem:
best = min(rem, key=lambda p: min(math.hypot(p.x - q.x, p.y - q.y) for q in conn))
tot += min(math.hypot(best.x - q.x, best.y - q.y) for q in conn)
rem.remove(best); conn.append(best)
return tot
plane = {'GND', '+3V3', 'VBAT'}
first = ['VREG_LX', 'DVDD', 'VREG_AVDD', 'XIN', 'XOUT_R', 'XOUT', 'USB_DP', 'USB_DM', 'USB_DP_C', 'USB_DM_C',
'SERVO1', 'SERVO2', 'SERVO3', 'SERVO4', 'SERVO5', 'SERVO6', 'SERVO7', 'SERVO8',
'SW_5V', 'BST_5V', 'SW_3V3', 'BST_3V3', 'VSYS', 'VBAT_IN', 'ISNS_P', 'ISNS_N', 'MAG_CAP', '+5V']
rest = sorted([n for n in R.netpads if n not in plane and n not in first and n not in routed_nets], key=mst_len)
order = [n for n in first if n in R.netpads and n not in routed_nets] + rest
done = 0
for net in order:
ok = R.route_net(net)
done += 1
L('%-12s %s (%d/%d, %.0fs)' % (net, 'ok' if ok else 'FAILED', done, len(order), time.time() - t0))
# 4) plane nets: connect every remaining pad (IC power pins) to the plane copper
for net in ('GND', '+3V3', 'VBAT'):
ok = R.route_net(net, max_nodes=400000)
L('%-12s %s (%.0fs)' % (net, 'ok' if ok else 'FAILED', time.time() - t0))
# 5) retry failures with a wider search
retry = list(R.unrouted); R.unrouted = []
L('retrying', len(retry))
for (net, ref, num) in retry:
ok = R.connect_pad(ref, num, via_cost=15.0, max_nodes=2500000)
L('retry %-12s %s.%s %s (%.0fs)' % (net, ref, num, 'ok' if ok else 'FAILED', time.time() - t0))
L('UNROUTED:', R.unrouted)
tr, vi = R.emit()
out = B.build(tracks=tr, vias=vi, out=B.OUT + '/AFS_Adom.kicad_pcb')
json.dump(dict(plan=R.plan, unrouted=R.unrouted, stub_failures=fails), open(B.OUT + '/outputs/route_plan.json', 'w'))
L('wrote', out, len(tr), 'segments', len(vi), 'vias in %.0fs' % (time.time() - t0))