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
tests/helpers/assertOuterMotorPower.ts
import type { CircuitJson } from "circuit-json";
/** Recompute motor-net membership, then inspect the actual routed wire layers. */
export function assertOuterMotorPower(json: CircuitJson) {
const j = json as any[];
const parent = new Map<string, string>();
const root = (id: string): string => {
if (!parent.has(id)) parent.set(id, id);
if (parent.get(id) !== id) parent.set(id, root(parent.get(id)!));
return parent.get(id)!;
};
for (const e of j) {
const ids = e.type === "source_trace"
? [...e.connected_source_port_ids, ...e.connected_source_net_ids]
: e.type === "source_component_internal_connection" ? e.source_port_ids : [];
for (const id of ids.slice(1)) parent.set(root(id), root(ids[0]));
}
const port = (name: string, pin: string) => {
const c = j.find(e => e.type === "source_component" && e.name === name);
const p = j.find(e => e.type === "source_port" && e.source_component_id === c?.source_component_id &&
(e.name === pin || e.port_hints?.includes(pin)));
if (!p) throw new Error(`Missing motor terminal ${name}.${pin}`);
return p;
};
const terminals = [
["J_MOTOR_USB", "A4B9"], ["F_MOTOR_VBUS", "pin2"], ["DRIVER", "VMA"],
...["AOUT1", "AOUT2", "BOUT1", "BOUT2", "ISENA", "ISENB"].map(p => ["DRIVER", p]),
["R_PHASE_A", "pin2"], ["R_PHASE_B", "pin2"],
];
const nets = new Set(terminals.map(([n, p]) => root(port(n!, p!).source_port_id)));
if (nets.size !== 11) throw new Error("Motor power nets are shorted or missing");
const sources = new Map(j.filter(e => e.type === "source_trace").map(e => [e.source_trace_id, e]));
const lengths: Record<string, number> = {top: 0, bottom: 0};
const checked: string[] = [];
const checkedNets = new Set<string>();
for (const t of j.filter(e => e.type === "pcb_trace")) {
const s = sources.get(t.source_trace_id);
const ids = s ? [...s.connected_source_port_ids, ...s.connected_source_net_ids] : t.source_net_id ? [t.source_net_id] : [];
if (!ids.length || !nets.has(root(ids[0]))) continue;
checked.push(t.pcb_trace_id); checkedNets.add(root(ids[0]));
for (const p of t.route) {
if (p.route_type === "wire" && !["top", "bottom"].includes(p.layer)) {
throw new Error(`Motor-current copper on inner layer: ${s?.name ?? t.pcb_trace_id} / ${p.layer}`);
}
if (p.route_type === "through_pad" &&
(!["top", "bottom"].includes(p.start_layer) || !["top", "bottom"].includes(p.end_layer))) {
throw new Error(`Motor-current path enters inner copper at plated pad: ${t.pcb_trace_id}`);
}
}
for (let i = 1; i < t.route.length; i++) {
const a=t.route[i-1], b=t.route[i];
if (a.route_type === "wire" && b.route_type === "wire" && a.layer === b.layer) {
lengths[a.layer]! += Math.hypot(b.x-a.x, b.y-a.y);
}
}
}
if (checkedNets.size !== nets.size) throw new Error("Motor power nets are missing routed copper");
return {status: "pass", motorNets: nets.size, checkedTraceCount: checked.length, checkedTraceIds: checked,
wireLengthMmByLayer: lengths, allowedWireLayers: ["top", "bottom"], throughViasAllowed: true};
}
