imrishabh18/rp2040-motor-controller

An RP2040-based NEMA17 stepper-motor controller with USB-C programming and PD motor-power negotiation, DRV8825 dual H-bridge drive, current/temperature telemetry, protected power filtering, status RGB LED, buzzer alarm, and optional magnetic encoder.

Version
1.0.42
License
unset
Stars
3

scripts/export-freerouting.py

from freerouting_geometry import *
structure=[];images=[];stacks=[];places=[];wires=[];pins={};portmap={};keepouts=[];netmap={}
board=next(e for e in j if e['type']=='pcb_board');border=Polygon([(p['x'],p['y']) for p in board['outline']]).buffer(-.1)
structure.append('(boundary (path pcb 0 '+' '.join(xy(x,y) for x,y in border.exterior.coords)+'))')
# Preserve fixed Kelvin leads as copper obstacles while routing only the load terminals.
sense=[e for e in j if e['type']=='pcb_trace' and '_SENSE_' in st[e['source_trace_id']].get('name','')]
sense_ids={e['pcb_trace_id'] for e in sense};sense_input_ports={p['pcb_port_id'] for p in pp.values() if sc.get(sp[p['source_port_id']]['source_component_id'],{}).get('name','').startswith('U_CURRENT_') and sp[p['source_port_id']]['pin_number'] in [1,8]}
shunts=[e for e in j if e['type']=='pcb_smtpad' and e.get('pcb_port_id') in pp and sc.get(sp[pp[e['pcb_port_id']]['source_port_id']]['source_component_id'],{}).get('name','').startswith('R_PHASE_')]
from shapely.ops import unary_union
shuntgeo=unary_union([geom(e).buffer(.03) for e in shunts])
for e in sense:
 for a,b,l,w in trace_segments(e['route']):
  g=LineString([a,b]).buffer(w/2+.005,quad_segs=8).difference(shuntgeo)
  for poly in ([g] if g.geom_type=='Polygon' else g.geoms):
   if not poly.is_empty:keepouts.append(f'(keepout KELVIN_{len(keepouts)} {polygon(poly,l)})')
for e in j:
 typ=e['type']
 if typ in ['pcb_smtpad','pcb_plated_hole']:
  n=enet(e);pid=e[typ+'_id'];g=geom(e,'outer_' if typ=='pcb_plated_hole' else '')
  ls=e.get('layers',[e.get('layer')]);comp='P'+str(len(images));padstack='S'+str(len(stacks));pport=e.get('pcb_port_id')
  if pport in sense_input_ports:
   keepouts.append(f'(keepout SENSEPIN_{pid} {polygon(g,ls[0])})');continue
  stacks.append(f'(padstack {padstack} '+''.join('(shape '+polygon(translate(g,-e['x'],-e['y']),l)+')' for l in ls)+' (attach off))')
  images.append(f'(image {comp} (pin {padstack} 1 0 0))');places.append(f'(component {comp} (place {comp} {xy(e['x'],e['y'])} front 0))');pins.setdefault(n,[]).append(comp+'-1')
  portmap[comp]={'element':pid,'pcb_port_id':pport,'net':n};netmap.setdefault(n,[]).append(pid)
  if typ=='pcb_plated_hole':
   drill=geom(e,'hole_').buffer(.55) # .45 drill spacing + via radius .15 - copper radius .25
   for l in layers:keepouts.append(f'(via_keepout PH_{pid}_{l} {polygon(drill,l)})')
 elif typ=='pcb_hole':
  g=geom(e,'hole_' if 'hole_diameter' in e else '')
  for l in layers:keepouts.append(f'(keepout H_{e[typ+"_id"]}_{l} {polygon(g.buffer(.2),l)})')
 elif typ=='pcb_trace' and e['pcb_trace_id'] not in sense_ids:
  n=enet(e)
  for a,b,l,w in trace_segments(e['route']):
   if math.dist(a,b)>1e-7:wires.append(f'(wire (path {l} {u(w)} {xy(*a)} {xy(*b)}) (net {n}) (type protect))')
 elif typ=='pcb_via':
  n=enet(e);wires.append(f'(via VFIX {xy(e["x"],e["y"])} (net {n}) (type protect))')
# Through vias only. The fixed 0.45-mm pads remain in the board source.
for name,diam in [('VIA',.5),('VFIX',.45)]:stacks.append(f'(padstack {name} '+''.join(f'(shape (circle {l} {u(diam)}))' for l in layers)+' (attach off))')
motor=set();wide={}
for c in plan['expectedInput']['connections']:
 ids=[p.get('pcb_port_id') for p in c['pointsToConnect'] if p.get('pcb_port_id') in pp]
 if ids:wide[enet(pp[ids[0]])]=max(wide.get(enet(pp[ids[0]]),.15),c.get('width',c.get('nominalTraceWidth',.15)))
for t in st.values():
 if t.get('name') in ['MOTOR_A_PLUS','MOTOR_A_MINUS','MOTOR_B_PLUS','MOTOR_B_MINUS','CURRENT_A_WINDING','CURRENT_B_WINDING']:motor.add(root(t['connected_source_port_ids'][0]))
classes=[]
for n in pins:
 width=.35 if n in motor else .15
 classes.append(f'(class C{len(classes)} {n} (circuit (use_via VIA)) (rule (width {u(width)}) (clearance 110)))')
text='(pcb ina240\n(parser (string_quote ") (space_in_quoted_tokens on) (host_cad tscircuit))\n(resolution um 10) (unit um)\n(structure\n'+''.join(f'(layer {l} (type signal) (property (index {i})))' for i,l in enumerate(layers))+'\n'+'\n'.join(structure+keepouts)+'\n(via VIA) (rule (width 150) (clearance 110) (clearance 130 (type via_smd)) (clearance 151 (type via_wire)) (clearance 205 (type via_via)) (clearance 100 (type smd_smd)))\n)\n(placement '+'\n'.join(places)+')\n(library '+'\n'.join(images+stacks)+')\n(network '+'\n'.join(f'(net {n} (pins '+ ' '.join(p)+'))' for n,p in pins.items())+'\n'+'\n'.join(classes)+')\n(wiring '+'\n'.join(wires)+')\n)\n'
Path('work/freerouting/board.dsn').write_text(text);Path('work/freerouting/mapping.json').write_text(json.dumps({'portmap':portmap,'netmap':netmap,'motor':list(motor)},indent=2))
print('Exported',len(images),'pins,',len(pins),'nets,',len(wires),'fixed wire/via objects,',len(keepouts),'keepouts; classes',len(classes))