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.41
- 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')
