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/audit-kelvin-connections.py

"""Verify load-current copper does not share the thin Kelvin sense branches."""
import hashlib,json,sys
from pathlib import Path
from shapely.geometry import Point,LineString,box
from shapely.affinity import rotate,translate
from shapely.ops import unary_union
from copper_geometry import trace_segments

path=Path(sys.argv[1] if len(sys.argv)>1 else 'dist/index/circuit.json')
j=json.loads(path.read_text())
components={e['name']:e['source_component_id'] for e in j if e['type']=='source_component'}
sources={e['name']:e for e in j if e['type']=='source_trace' and e.get('name')}
def copper(name,layer='top'):
 sid=sources[name]['source_trace_id'];parts=[]
 for t in j:
  if t['type']!='pcb_trace' or t.get('source_trace_id')!=sid:continue
  for a,b,l,w in trace_segments(t['route']):
   if l==layer:parts.append(LineString([a,b]).buffer(w/2,quad_segs=48))
  for p in t['route']:
   if p['route_type']=='via':parts.append(Point(p['x'],p['y']).buffer(p.get('via_diameter',.5)/2,quad_segs=48))
 assert parts,f'No copper for {name}'
 return unary_union(parts)
def pad(component,pin):
 sp=next(e for e in j if e['type']=='source_port' and e['source_component_id']==components[component] and e['pin_number']==pin)
 pp=next(e for e in j if e['type']=='pcb_port' and e.get('source_port_id')==sp['source_port_id'])
 e=next(e for e in j if e['type']=='pcb_smtpad' and e.get('pcb_port_id')==pp['pcb_port_id'])
 w,h=e['width'],e['height'];r=min(w,h)/2 if 'pill' in e['shape'] else e.get('corner_radius',0)
 g=LineString([(-w/2+r,-h/2+r),(w/2-r,h/2-r)]).buffer(r,quad_segs=48) if r else box(-w/2,-h/2,w/2,h/2)
 return translate(rotate(g,e.get('ccw_rotation',0),origin=(0,0)),e['x'],e['y'])
rows=[]
for phase in ['A','B']:
 for polarity,shunt_pin,amp_pin,power in [('POS',1,8,f'MOTOR_{phase}_PLUS'),('NEG',2,1,f'CURRENT_{phase}_WINDING')]:
  name=f'CURRENT_{phase}_SENSE_{polarity}';load=copper(power)
  branch=copper(name).difference(pad(f'R_PHASE_{phase}',shunt_pin).buffer(.02))
  rows.append({'branch':name,'powerPath':power,'separationOutsideShuntMm':load.distance(branch),'sharedCopperOutsideShunt':load.intersects(branch),'amplifierPadTouchedByLoadPath':load.intersects(pad(f'U_CURRENT_{phase}',amp_pin))})
report={'circuitJsonSha256':hashlib.sha256(path.read_bytes()).hexdigest(),'branches':rows}
Path('reports/kelvin-connectivity.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report,indent=2))
if any(r['sharedCopperOutsideShunt'] or r['amplifierPadTouchedByLoadPath'] for r in rows):sys.exit('Kelvin connection audit failed')