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/prepare-round9-motor-block.mjs

import fs from "node:fs"
import assert from "node:assert/strict"
import { repositionPcbComponentTo, transformPcbElements } from "@tscircuit/circuit-json-util"
import { rotateDEG } from "transformation-matrix"
const base="../../checkpoints/nema17-round8-validated-local-20261020-ii8Wrw/restore-verified"
const read=p=>JSON.parse(fs.readFileSync(p,"utf8"))
const c=read(process.env.ROUND9_PLACEMENT_BASE??`${base}/dist/index/circuit.json`),original=structuredClone(c)
const small=process.env.ROUND9_BLOCK==="small"
const stem=small?"round9-small":"round9-block"
const moves=small?{C_BUCK_OUT1:{x:-17.7,y:-9,rotation:90,layer:"bottom"},
  C_ETH_RXP_AC:{x:-14.75,y:-5.6,rotation:180,layer:"bottom"},
  R_ETH_RAIL_AB:{x:10.3232,y:-13.4895,rotation:90,layer:"bottom"}}:{R_ISEN_B:{x:Number(process.env.ROUND9_B_X??5),y:Number(process.env.ROUND9_B_Y??-13.9),rotation:270,layer:"bottom"},
  C_REF:{x:7.9,y:-15,rotation:180,layer:"bottom"},
  R_ISET_210:{x:7.9,y:-13.9,rotation:180,layer:"bottom"},
  C_ETH_7:{x:-2,y:-1.48,rotation:270,layer:"top"}}
const movedIds=new Set()
for(const [name,move] of Object.entries(moves)){
  const s=c.find(e=>e.type==="source_component"&&e.name===name)
  const p=c.find(e=>e.type==="pcb_component"&&e.source_component_id===s.source_component_id)
  assert.equal(p.layer,move.layer)
  if(p.rotation!==move.rotation)transformPcbElements(c.filter(e=>e===p||e.pcb_component_id===p.pcb_component_id),rotateDEG(move.rotation-p.rotation,p.center.x,p.center.y))
  movedIds.add(s.source_component_id)
  repositionPcbComponentTo(c,p.pcb_component_id,move)
}
const movedPorts=new Set(c.filter(e=>e.type==="source_port"&&movedIds.has(e.source_component_id)).map(e=>e.source_port_id))
const affected=c.filter(e=>e.type==="source_trace"&&e.connected_source_port_ids.some(id=>movedPorts.has(id))).map(e=>e.source_trace_id)
if(!small)affected.push("source_trace_334","source_trace_336")
const sources=new Map(c.filter(e=>e.type==="source_trace").map(e=>[e.source_trace_id,e]))
let retained=read(process.env.ROUND9_REPLAY_BASE??`${base}/routing/complete-routes.json`).filter(t=>small||!affected.includes(t.source_trace_id))
// Retire exactly the wide VM crossbar; all other junctions and its actual
// endpoint barrels remain. A new >=0.8 mm outer-layer bridge is required.
const same=(p,x,y)=>Math.hypot(p.x-x,p.y-y)<1e-6
const fragments=[]
let cuts=0
for(const t of retained){
  let run=[]
  for(let i=0;i<t.route.length;i++){
    const a=t.route[i],b=t.route[i+1];run.push(a)
    if(!small&&b&&a.route_type===b.route_type&&a.route_type==="wire"&&a.layer===b.layer&&a.layer==="bottom"&&
      ((same(a,-1.8,-16)&&same(b,7,-16))||(same(b,-1.8,-16)&&same(a,7,-16)))){
      assert.equal(a.width,.8);fragments.push({...t,route:run});run=[];cuts++
    }
  }
  if(run.length)fragments.push({...t,route:run})
}
assert.ok(small||cuts>0)
retained=fragments.map((t,i)=>({...t,pcb_trace_id:`round9_retained_${i}`}))
// Real, authored top-side driver escapes, ending in full-stack through vias.
for(const [sid,pin,x] of (small?[]:[["source_trace_334",6,.224894],["source_trace_336",9,2.175106]])){
  const part=c.find(e=>e.type==="source_component"&&e.name==="U_MOTOR")
  const sp=c.find(e=>e.type==="source_port"&&e.source_component_id===part.source_component_id&&e.pin_number===pin)
  const p=c.find(e=>e.type==="pcb_port"&&e.source_port_id===sp.source_port_id)
  retained.push({type:"pcb_trace",pcb_trace_id:`round9_escape_${pin}`,source_trace_id:sid,
    subcircuit_id:sources.get(sid).subcircuit_id,
    route:[{route_type:"wire",layer:"top",width:.3,x:p.x,y:p.y,start_pcb_port_id:p.pcb_port_id},
      {route_type:"wire",layer:"top",width:.3,x,y:-15.6},
      {route_type:"via",x,y:-15.6,from_layer:"top",to_layer:"bottom",via_diameter:.45,via_hole_diameter:.3}]})
}
const jobs=affected.filter((s,i,a)=>a.indexOf(s)===i).map(source=>({source,
  constraints:["source_trace_334","source_trace_336","source_trace_337"].includes(source)?{min_trace_thickness:source==="source_trace_337"?.4:.3,allowed_layers:["top","bottom"]}:{}}))
if(small)for(const j of jobs)if(["source_trace_111","source_trace_114","source_trace_115"].includes(j.source))j.constraints.allowed_layers=["top","bottom"]
// Current returns first, then the reference/bypass leaves. VM crosses only
// after the real new shunt copper has claimed its required clearances.
const priority=s=>({source_trace_249:-2,source_trace_250:-1,source_trace_334:1,source_trace_336:0,source_trace_337:2,
  source_trace_330:3,source_trace_393:4,source_trace_331:5,source_trace_394:6,
  source_trace_190:7,source_trace_149:8}[s]??9)
jobs.sort((a,b)=>priority(a.source)-priority(b.source))
const endpoint=(x,y,label)=>({x,y,layers:["top","bottom"],pcb_port_id:label})
if(!small)jobs.unshift({source:"source_trace_305",constraints:{min_trace_thickness:.5,allowed_layers:["top","bottom"]},
  start:endpoint(-1.8,-16,"real_VM_west_barrel"),target:endpoint(7,-16,"real_VM_east_barrel")})
const write=(p,o)=>fs.writeFileSync(p,JSON.stringify(o,null,2)+"\n")
write(`routing/${stem}-moves.json`,moves)
if(!small)write("routing/round9-placement.json",moves)
write(`routing/${stem}-placement.circuit.json`,c)
write(`routing/${stem}-retained.json`,retained)
write(`routing/${stem}-plan.json`,{prefix:small?"round9-small":"round9-motorblock",candidateOnly:true,
  movesFile:`routing/${stem}-moves.json`,placement:`routing/${stem}-placement.circuit.json`,
  retained:`routing/${stem}-retained.json`,extent:[-20.9,-20.9,20.9,20.9],jobs})
console.log(JSON.stringify({moves,affected,cuts,jobs:jobs.map(j=>j.source)}))