shibosoftwaredev/f1c100s-linux-nema17-stepper-controller

A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.

Version
0.4.56
License
unset
Stars
0

scripts/audit-session-kelvin.py

"""Preflight Kelvin separation on session metal; emitted audit still required."""
from route_geometry import *
from shapely.ops import unary_union
c=load_circuit();root=connectivity(c);m=json.loads(Path('routing/net-map.json').read_text());cu=json.loads(Path('routing/physical-copper.json').read_text())
names={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'}
sp={e['source_port_id']:e for e in c if e['type']=='source_port'}
ports={e['pcb_port_id']:e for e in c if e['type']=='pcb_port'}
def p(name,pin):return next(e for e in ports.values() if names[sp[e['source_port_id']]['source_component_id']]==name and sp[e['source_port_id']].get('pin_number')==pin)
def connected(copper,vias,a,b,cut):
    regions=[]
    for l,items in copper.items():
        g=unary_union(items).difference(cut)
        regions.extend((l,h) for h in ([g] if g.geom_type=='Polygon' else getattr(g,'geoms',[])) if not h.is_empty)
    parent=list(range(len(regions)))
    def find(i):
        while parent[i]!=i:parent[i]=parent[parent[i]];i=parent[i]
        return i
    for v in vias:
        ids=[i for i,(l,h) in enumerate(regions) if h.intersects(v.difference(cut))]
        for i in ids[1:]:parent[find(i)]=find(ids[0])
    def contacts(port):return {find(i) for i,(l,h) in enumerate(regions) if l in port['layers'] and h.covers(Point(port['x'],port['y']))}
    aa,bb=contacts(a),contacts(b);assert aa and bb,'terminal missing from session'
    return bool(aa&bb)
result=[]
for phase,driver,motor in [('A',5,4),('B',10,1)]:
    for pol,apin,rpin,other in [('POS',8,1,p('U_MOTOR',driver)),('NEG',1,2,p('J_MOTOR',motor))]:
        amp=p('U_CURRENT_'+phase,apin);shunt=p('R_INLINE_'+phase,rpin);net=m['roots'][root(amp['source_port_id'])]
        copper={l:[] for l in LAYERS};vias=[]
        for e in c:
            if e['type'] in ['pcb_smtpad','pcb_plated_hole'] and e.get('pcb_port_id') in ports and m['roots'].get(root(ports[e['pcb_port_id']]['source_port_id']))==net:
                for l in e.get('layers',[e.get('layer')]):copper[l].append(geometry(e,'outer_' if e['type']=='pcb_plated_hole' else ''))
        for w in cu['wires']:
            if w['net']==net:copper[w['layer']].append(LineString(w['points']).buffer(w['width']/2))
        for v in cu['vias']:
            if v['net']==net:
                g=Point(v['x'],v['y']).buffer(.225);vias.append(g)
                for l in LAYERS:copper[l].append(g)
        land=unary_union([geometry(e) for e in c if e['type']=='pcb_smtpad' and e.get('pcb_port_id')==shunt['pcb_port_id']]).buffer(.0002)
        joined=connected(copper,vias,amp,other,Polygon())
        separate=not connected(copper,vias,amp,other,land)
        result.append({'phase':phase,'polarity':pol,'connected':joined,'joinsOnlyAtShuntLand':separate})
Path('reports/coil-session-kelvin.json').write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps(result));assert all(r['connected'] and r['joinsOnlyAtShuntLand'] for r in result),'Kelvin preflight failed'