imrishabh18/rp2040-motor-controller
This circuit incorporates a USB-C Power Delivery (PD) interface with a CH224K controller, a WJ500V-5.08-04P motor output connector, multiple power conditioning components including capacitors, resistors, diodes, and an RP2040 microcontroller, to manage motor control, signal routing, and power management for a USB-powered stepper motor system.
- Version
- 1.0.14
- License
- unset
- Stars
- 0
ground-fix.patch
--- a/package.json
+++ b/package.json
@@ -16,30 +16,38 @@
"snapshot:update": "tsci snapshot --update",
"start": "tsci dev",
"test": "bun test",
- "check:shorts": "tsci check shorts index.circuit.tsx --mode gerber --layer all",
- "typecheck": "tsc --noEmit"
+ "check:shorts": "tsci check shorts dist/index/circuit.json --mode gerber --layer all",
+ "typecheck": "tsc --noEmit",
+ "validate:build": "bun scripts/validate-build.ts",
+ "test:built": "CIRCUIT_JSON_PATH=dist/index/circuit.json bun test",
+ "render:schematic": "bun scripts/render-schematic.tsx",
+ "check": "tsci check dist/index/circuit.json",
+ "verify": "bun run typecheck && bun run build && bun run validate:build && bun run test:built && bun run check:built",
+ "check:built": "bun scripts/check-built.ts"
},
"devDependencies": {
- "@tscircuit/checks": "^0.0.171",
+ "@tscircuit/checks": "0.0.183",
"@types/bun": "^1.3.14",
- "@types/react": "^19.2.18",
+ "@types/react": "^19.2.17",
"bun-match-svg": "0.0.15",
- "circuit-to-svg": "0.0.391",
- "tscircuit": "^0.0.2463",
- "typescript": "^5.0.0"
+ "circuit-to-svg": "0.0.413",
+ "tscircuit": "0.0.2463",
+ "typescript": "^5.0.0",
+ "circuit-json": "0.0.485"
},
"dependencies": {
- "@tscircuit/common": "^0.0.48",
- "@tscircuit/core": "^0.0.1768",
+ "@tscircuit/common": "0.0.48",
+ "@tscircuit/core": "0.0.1844",
"@tscircuit/footprinter": "^0.0.426",
"iobuffer": "^6.0.1"
},
"overrides": {
- "@tscircuit/capacity-autorouter": "0.0.845",
- "@tscircuit/core": "0.0.1768"
+ "@tscircuit/core": "0.0.1844",
+ "@tscircuit/cli": "0.1.2021",
+ "circuit-json": "0.0.485",
+ "circuit-to-svg": "0.0.413"
},
"patchedDependencies": {
- "@tscircuit/cli@0.1.1757": "patches/@tscircuit%2Fcli@0.1.1757.patch",
"@tscircuit/common@0.0.48": "patches/@tscircuit%2Fcommon@0.0.48.patch"
}
-}+}
--- a/index.circuit.tsx
+++ b/index.circuit.tsx
@@ -34,7 +34,8 @@
routingDisabled?: boolean;
} = {}) {
return (
- <board
+ <board
+ routingDisabled={routingDisabled}
layers={2}
outline={[
{ x: -25, y: -25 },
@@ -43,7 +44,7 @@
{ x: -25, y: 24 },
]}
minViaHoleDiameter="0.3mm"
- minViaPadDiameter="0.45mm"
+ minViaPadDiameter="0.45mm"
autorouterEffortLevel="10x"
autorouterVersion="beta_pipeline7"
autorouter={{
--- a/schematic/MotorDriverCoreSection.tsx
+++ b/schematic/MotorDriverCoreSection.tsx
@@ -200,6 +200,8 @@
/>
<trace
name="DRIVER_FAULT"
+ // Reserve the complete MCU/pull-up/driver fault route before power routing.
+ routingPhaseIndex={-2}
from=".MCU > .U1 > .GPIO23"
to=".DRIVER > .nFault"
thickness="0.1mm"
@@ -227,6 +229,14 @@
export const MotorDriverPrimaryGroundTraces = () => (
<>
+ {/* Join the board's scoped GND (pours and sense returns) to the
+ MCU/driver/USB ground network. Matching net names do not join them. */}
+ <trace
+ name="BOARD_GND_JOIN"
+ from=".DRIVER > .GND2"
+ to="net.GND"
+ {...powerTrace}
+ />
<trace
name="COMMON_GND"
from=".DRIVER > .GND2"
@@ -279,6 +289,14 @@
export const MotorDriverSenseAndControlTraces = () => (
<>
+ {/* Explicit layer transition keeps the sense escape clear of BOUT2. */}
+ <via name="ISEN_B_ESCAPE" pcbX={10.275} pcbY={-4.50}
+ holeDiameter="0.3mm" outerDiameter="0.45mm"
+ fromLayer="top" toLayer="bottom"
+ connectsTo={[".DRIVER > .BISEN", ".R_ISEN_B > .pin1", ".ISEN_B_ESCAPE > .top", ".ISEN_B_ESCAPE > .bottom"]} />
+ <trace name="ISEN_B_ESCAPE_TRACE"
+ from=".DRIVER > .BISEN" to=".ISEN_B_ESCAPE > .top"
+ thickness="0.2mm" pcbPath={[".ISEN_B_ESCAPE > .top"]} />
<trace
name="ISEN_A"
from=".DRIVER > .AISEN"
@@ -288,7 +306,7 @@
/>
<trace
name="ISEN_B"
- from=".DRIVER > .BISEN"
+ from=".ISEN_B_ESCAPE > .bottom"
to=".R_ISEN_B > .pin1"
schDisplayLabel="B_ISEN"
{...powerTrace}
@@ -339,6 +357,7 @@
/>
<trace
name="FAULT_PULLUP"
+ routingPhaseIndex={-2}
from=".R_FAULT_PU > .pin1"
to=".DRIVER > .nFault"
schDisplayLabel="nFAULT"
--- a/schematic/MotorOutputsSection.tsx
+++ b/schematic/MotorOutputsSection.tsx
@@ -25,8 +25,11 @@
export const MotorOutputTraces = () => (
<>
+ <autoroutingphase phaseIndex={-1} minTraceToPadEdgeClearance="0.2mm" />
+ {/* Reserve room for the wider motor routes before the remaining signals. */}
<trace
name="MOTOR_A_PLUS"
+ routingPhaseIndex={-1}
from=".DRIVER > .AOUT1"
to=".J1 > .pin1"
schDisplayLabel="A+"
@@ -34,13 +37,16 @@
/>
<trace
name="MOTOR_A_MINUS"
+ routingPhaseIndex={-1.5}
from=".DRIVER > .AOUT2"
to=".J1 > .pin2"
schDisplayLabel="A-"
{...motorTrace}
+
/>
<trace
name="MOTOR_B_PLUS"
+ routingPhaseIndex={-1}
from=".DRIVER > .BOUT1"
to=".J1 > .pin3"
schDisplayLabel="B+"
@@ -48,6 +54,7 @@
/>
<trace
name="MOTOR_B_MINUS"
+ routingPhaseIndex={-1}
from=".DRIVER > .BOUT2"
to=".J1 > .pin4"
schDisplayLabel="B-"
--- a/schematic/SchematicSheets.tsx
+++ b/schematic/SchematicSheets.tsx
@@ -5,6 +5,7 @@
<schematicsheet
name={schematicSheets.controller}
displayName="RP2040 Controller"
+ sheetWidth="310mm"
sheetIndex={1}
/>
--- a/tests/board.test.tsx
+++ b/tests/board.test.tsx
@@ -1,21 +1,27 @@
import { expect, test } from "bun:test";
+import { readFileSync } from "node:fs";
import {
checkPadTraceClearance,
checkSourceTracesMatchPcbTraceThickness,
runAllRoutingChecks,
} from "@tscircuit/checks";
-import type { PcbSmtPad, PcbTrace, PcbVia, SourceTrace } from "circuit-json";
+import type { CircuitJson, PcbSmtPad, PcbTrace, PcbVia, SourceTrace } from "circuit-json";
import { Circuit } from "tscircuit";
import Rp2040MotorController from "../index.circuit";
import { routedViaOverlapsPad } from "./helpers/routedViaOverlapsPad";
+import { assertCommonGround } from "./helpers/assertCommonGround";
test("renders the complete RP2040 stepper-motor controller", async () => {
- const circuit = new Circuit();
- circuit.add(<Rp2040MotorController routingDisabled={false} />);
-
- await circuit.renderUntilSettled();
-
- const circuitJson = circuit.getCircuitJson();
+ let circuitJson: CircuitJson;
+ if (process.env.CIRCUIT_JSON_PATH) {
+ circuitJson = JSON.parse(readFileSync(process.env.CIRCUIT_JSON_PATH, "utf8"));
+ } else {
+ const circuit = new Circuit();
+ circuit.add(<Rp2040MotorController routingDisabled={false} />);
+ await circuit.renderUntilSettled();
+ circuitJson = circuit.getCircuitJson();
+ }
+ assertCommonGround(circuitJson);
const crystalTraceNames = ["XIN", "XOUT_R", "XOUT", "CXIN", "CXOUT"];
const crystalSourceTraces = circuitJson.filter(
(element): element is SourceTrace =>
@@ -342,7 +348,7 @@
const rIsenBSourceTrace = circuitJson.find(
(element) =>
element.type === "source_trace" &&
- element.display_name === ".DRIVER > .BISEN to .R_ISEN_B > .pin1",
+ element.name === "ISEN_B",
);
const rIsenBSourceTraceId =
rIsenBSourceTrace?.type === "source_trace"
@@ -351,7 +357,7 @@
const rIsenBPcbTrace = circuitJson.find(
(element) =>
element.type === "pcb_trace" &&
- element.source_trace_id === rIsenBSourceTraceId,
+ (element.source_trace_id === rIsenBSourceTraceId || "connection_name" in element && element.connection_name === rIsenBSourceTraceId),
);
let upperMotorALength = 0;
let upperMotorANominalLength = 0;
--- /dev/null
+++ b/tests/common-ground.test.tsx
@@ -0,0 +1,19 @@
+import { expect, test } from "bun:test";
+import { Circuit } from "tscircuit";
+import Rp2040MotorController from "../index.circuit";
+import { assertCommonGround } from "./helpers/assertCommonGround";
+
+test("all ground domains share electrical connectivity, and the missing join is detected", async () => {
+ const circuit = new Circuit({ platform: { pcbDisabled: true } });
+ circuit.add(<Rp2040MotorController routingDisabled />);
+ await circuit.renderUntilSettled();
+ const json = circuit.getCircuitJson();
+ expect(assertCommonGround(json).checkedGroundPorts).toBe(18);
+ expect(json.filter((e) => (e.type === "schematic_net_label" || e.type === "schematic_text") &&
+ e.text === "GND" && e.display_superscript)).toEqual([]);
+
+ // Recreate the original omission. Same-name labels and stale connectivity
+ // keys must not make the disconnected source network appear connected.
+ const withoutJoin = json.filter((e) => !(e.type === "source_trace" && e.name === "BOARD_GND_JOIN"));
+ expect(() => assertCommonGround(withoutJoin)).toThrow("Disconnected GND networks");
+}, 60_000);
--- /dev/null
+++ b/tests/helpers/assertCommonGround.ts
@@ -0,0 +1,78 @@
+import type { CircuitJson, SourceNet } from "circuit-json";
+
+/** Follow electrical edges only: names and cached connectivity keys are not edges. */
+export function assertCommonGround(json: CircuitJson) {
+ const parent = new Map<string, string>();
+ const root = (id: string): string => {
+ if (!parent.has(id)) parent.set(id, id);
+ const next = parent.get(id)!;
+ if (next !== id) parent.set(id, root(next));
+ return parent.get(id)!;
+ };
+ for (const element of json) {
+ const ids = element.type === "source_trace"
+ ? [...element.connected_source_port_ids, ...element.connected_source_net_ids]
+ : element.type === "source_component_internal_connection"
+ ? element.source_port_ids : [];
+ for (const id of ids.slice(1)) parent.set(root(id), root(ids[0]!));
+ }
+ const nets = json.filter((e): e is SourceNet => e.type === "source_net" && e.name === "GND");
+ if (!nets.length) throw new Error("Missing GND net");
+ const ground = root(nets[0]!.source_net_id);
+ for (const net of nets) {
+ if (root(net.source_net_id) !== ground) throw new Error("Disconnected GND networks");
+ }
+ const required = [
+ ["U1", "GND"], ["TP_GND", "pin1"],
+ ["DRIVER", "GND1"], ["DRIVER", "GND2"],
+ ["DRIVER", "MODE"], ["DRIVER", "TRQ"],
+ ["J_USB", "A1B12"], ["J_MOTOR_USB", "A1B12"],
+ ["U_PD", "GND"], ["R_ISEN_A", "pin2"], ["R_ISEN_B", "pin2"],
+ ["C_VM_BULK", "pin2"], ["C_VM_HF", "pin2"],
+ ["C_PD_VDD", "pin2"], ["C_PD_VBUS", "pin2"],
+ ["C_MOTOR_BULK", "pin2"], ["D_MOTOR_TVS", "pin1"],
+ ["R_SLEEP_PD", "pin2"],
+ ];
+ for (const [name, pin] of required) {
+ const component = json.find((e) => e.type === "source_component" && e.name === name);
+ if (component?.type !== "source_component") throw new Error(`Missing ${name}`);
+ const port = json.find((e) => e.type === "source_port" &&
+ e.source_component_id === component.source_component_id &&
+ (e.name === pin || e.port_hints?.includes(pin!)));
+ if (port?.type !== "source_port" || root(port.source_port_id) !== ground) {
+ throw new Error(`${name}.${pin} is not on common ground`);
+ }
+ }
+ for (const e of json) {
+ if (e.type === "pcb_copper_pour" && (!e.source_net_id || root(e.source_net_id) !== ground)) {
+ throw new Error("Copper pour is not assigned to common ground");
+ }
+ if (e.type === "source_net" && ["VBUS", "VSYS", "V3V3", "V1V1"].includes(e.name) &&
+ root(e.source_net_id) === ground) throw new Error(`${e.name} is shorted to ground`);
+ }
+ // Sense inputs must reach their own shunt, never bypass it to ground.
+ const portFor = (name: string, pin: string) => {
+ const component = json.find(e => e.type === "source_component" && e.name === name);
+ if (component?.type !== "source_component") throw new Error(`Missing ${name}`);
+ const port = json.find(e => e.type === "source_port" &&
+ e.source_component_id === component.source_component_id &&
+ (e.name === pin || e.port_hints?.includes(pin)));
+ if (port?.type !== "source_port") throw new Error(`Missing ${name}.${pin}`);
+ return root(port.source_port_id);
+ };
+ for (const channel of ["A", "B"]) {
+ const sense = portFor("DRIVER", `${channel}ISEN`);
+ if (sense === ground || sense !== portFor(`R_ISEN_${channel}`, "pin1")) {
+ throw new Error(`Invalid ${channel}-channel current-sense connection`);
+ }
+ }
+ const motorNets = ["AOUT1", "AOUT2", "BOUT1", "BOUT2"].map((pin, i) => {
+ const motor = portFor("DRIVER", pin);
+ if (motor === ground || motor !== portFor("J1", `pin${i + 1}`)) {
+ throw new Error(`Invalid motor output ${pin}`);
+ }
+ return motor;
+ });
+ if (new Set(motorNets).size !== 4) throw new Error("Motor outputs are shorted together");
+ return { groundNetDeclarations: nets.length, checkedGroundPorts: required.length, checkedCurrentSenseChannels: 2, checkedMotorOutputs: 4 };
+}
--- /dev/null
+++ b/scripts/validate-build.ts
@@ -0,0 +1,28 @@
+import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
+import { createHash } from "node:crypto";
+import { runAllRoutingChecks } from "@tscircuit/checks";
+import type { CircuitJson } from "circuit-json";
+import { assertCommonGround } from "../tests/helpers/assertCommonGround";
+
+const file = process.argv[2] ?? "dist/index/circuit.json";
+const raw = readFileSync(file, "utf8");
+const json: CircuitJson = JSON.parse(raw);
+const ground = assertCommonGround(json);
+const embeddedErrors = json.filter(e => e.type.endsWith("_error"));
+const routingErrors = (await runAllRoutingChecks(structuredClone(json)))
+ .filter(e => e.type.endsWith("_error"));
+const warnings: Record<string, number> = {};
+for (const e of json) if (e.type.endsWith("_warning")) warnings[e.type] = (warnings[e.type] ?? 0) + 1;
+const versions = Object.fromEntries([
+ "tscircuit", "@tscircuit/core", "@tscircuit/cli", "@tscircuit/common",
+ "@tscircuit/capacity-autorouter", "@tscircuit/checks", "circuit-json", "circuit-to-svg",
+].map(name => [name, JSON.parse(readFileSync(`node_modules/${name}/package.json`, "utf8")).version]));
+const report = {
+ generatedAt: new Date().toISOString(), input: file,
+ sha256: createHash("sha256").update(raw).digest("hex"), versions,
+ commonGround: ground, embeddedErrors, routingErrors, warnings,
+};
+mkdirSync("reports", { recursive: true });
+writeFileSync("reports/validation.json", JSON.stringify(report, null, 2) + "\n");
+console.log(JSON.stringify(report, null, 2));
+if (embeddedErrors.length || routingErrors.length) process.exitCode = 1;
--- /dev/null
+++ b/scripts/check-built.ts
@@ -0,0 +1,26 @@
+import { mkdirSync, writeFileSync } from "node:fs";
+
+// tsci check currently prints diagnostics without failing on embedded errors.
+// Gate the reported count as well as the process exit status.
+const file = process.argv[2] ?? "dist/index/circuit.json";
+mkdirSync("reports", { recursive: true });
+for (const [name, args] of [
+ ["tsci-check", ["check", file]],
+ ["shorts", ["check", "shorts", file, "--mode", "gerber", "--layer", "all"]],
+] as const) {
+ const process = Bun.spawn(["bunx", "--no-install", "tsci", ...args], {
+ stdout: "pipe", stderr: "pipe",
+ });
+ const [stdout, stderr, code] = await Promise.all([
+ new Response(process.stdout).text(),
+ new Response(process.stderr).text(),
+ process.exited,
+ ]);
+ const output = stdout + stderr;
+ writeFileSync(`reports/${name}.log`, output);
+ console.log(output);
+ if (code !== 0 || (name === "tsci-check" && !/^Errors: 0\s*$/m.test(output)) ||
+ (name === "shorts" && !output.includes("No shorts detected"))) {
+ throw new Error(`${name} failed; see reports/${name}.log`);
+ }
+}
--- /dev/null
+++ b/scripts/render-pcb.ts
@@ -0,0 +1,12 @@
+import { readFileSync, writeFileSync } from "node:fs";
+import { convertCircuitJsonToPcbSvg } from "circuit-to-svg";
+import { Resvg } from "@resvg/resvg-js";
+const json = JSON.parse(readFileSync("dist/index/circuit.json", "utf8"));
+for (const layer of ["top", "bottom"] as const) {
+ const svg = convertCircuitJsonToPcbSvg(json, {
+ layer, width: 1400, matchBoardAspectRatio: true,
+ shouldDrawErrors: true, showErrorsInTextOverlay: true,
+ });
+ writeFileSync(`dist/index/pcb-${layer}.svg`, svg);
+ writeFileSync(`dist/index/pcb-${layer}.png`, new Resvg(svg).render().asPng());
+}
--- /dev/null
+++ b/scripts/render-schematic.tsx
@@ -0,0 +1,12 @@
+import { Circuit } from "tscircuit";
+import { convertCircuitJsonToStackedSchematicSheetsSvg } from "circuit-to-svg";
+import { mkdirSync, writeFileSync } from "node:fs";
+import Board from "../index.circuit";
+const circuit = new Circuit({ platform: { pcbDisabled: true } });
+circuit.add(<Board routingDisabled />);
+await circuit.renderUntilSettled();
+const json = circuit.getCircuitJson();
+mkdirSync("dist/schematic", { recursive: true });
+writeFileSync("dist/schematic/circuit.json", JSON.stringify(json, null, 2));
+writeFileSync("dist/schematic/schematic.svg", convertCircuitJsonToStackedSchematicSheetsSvg(json, { width: 1400 }));
+console.log(json.filter(e => e.type.endsWith("_error") || e.type === "schematic_element_outside_sheet_warning"));
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,6 +14,7 @@
"resolveJsonModule": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
+ "allowJs": true,
"experimentalDecorators": true,
"types": ["tscircuit", "bun"]
},
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+.tscircuit/
+.cache/
+dist/
+*.log
+tests/__snapshots__/*.diff.png
--- a/README.md
+++ b/README.md
@@ -1,105 +1,55 @@
-# RP2040 Motor Controller
+# RP2040 motor controller — verified ground and routing correction
-A tscircuit board combining the reusable `Microcontroller_RP2040` from
-`@tscircuit/common` with a TI DRV8847 dual H-bridge configured for one bipolar
-stepper motor.
+This project is based on the published `imrishabh18/rp2040-motor-controller` v1.0.13 source. It fixes the two disconnected ground networks by adding `BOARD_GND_JOIN` from `DRIVER.GND2` to the board-level `net.GND`. The existing driver-to-MCU and USB ground connections remain in place.
-## Features
+The board-level ground contains the copper pours, motor current-sense returns, decoupling returns, MODE/TRQ pins, TVS return, and sleep pull-down. The new trace joins these to the MCU, driver, and both USB grounds. Its requested width is 0.8 mm, matching the existing power-trace target.
-- RP2040, USB-C, flash, crystal, 3.3 V regulation, boot/run controls, and SWD
- test points from `@tscircuit/common`
-- one four-wire bipolar stepper motor controlled by GPIO18-GPIO21
-- DRV8847 sleep control on GPIO22 and active-low fault feedback on GPIO23
-- dedicated USB-C motor-power input with a CH224K PD sink requesting 9 V
-- 1 A current regulation per coil using 0.15 ohm sense resistors
-- solder-mask-covered GND copper pours on the top and bottom layers
-- one four-position screw terminal labeled A+, A-, B+, and B-
-- four 3.2 mm mounting holes
+## Toolchain
-The RP2040 control circuitry and motor driver have separate USB-C ports. The
-motor port negotiates a 9 V USB PD contract when the source supports it; its
-VBUS drives the DRV8847 motor rail. The grounds are shared. The 9 V request is
-within the DRV8847's 2.7 V to 18 V operating range.
+Versions were resolved from npm on 2026-09-05:
-## Development
+- tscircuit: **0.0.2463**
+- @tscircuit/core: **0.0.1844** (includes schematic superscripts for disconnected same-name nets)
+- @tscircuit/cli: **0.1.2021**
+- @tscircuit/checks: **0.0.183**
+- circuit-json: **0.0.485**
+- circuit-to-svg: **0.0.413**
+
+The existing patched `@tscircuit/common@0.0.48` is intentionally retained: that patch contains this board's RP2040 placement, footprints, crystal, decoupling, and schematic-sheet customizations. Replacing it with an unmodified module would change the hardware layout. Obsolete compiler/router overrides and the old CLI patch are no longer active. `bun.lock` records the installed dependency graph.
+
+## Reproduce
```sh
-bun install
+bun install --frozen-lockfile
bun run typecheck
-bun test
+bun test tests/common-ground.test.tsx
bun run build
-bun run snapshot:update
+bun run validate:build
+bun run test:built
+bun run check:built
+bun run render:schematic
+bun scripts/render-pcb.ts
```
-The DRV8847 support network includes local 10 uF and 100 nF VM decoupling and
-separate 0.15 ohm current-sense resistors for the two coils. The selected
-DRV8847PWPR is JLCPCB/LCSC part C544361. The four-position 5.08 mm stepper
-terminal is WJ500V-5.08-04P-14-00A, JLCPCB/LCSC part C42377749.
+`validate:build` reads the built Circuit JSON and writes `reports/validation.json`, including a SHA-256 hash, installed versions, ground checks, and independently rerun routing checks. Treat this validation report as authoritative: the CLI can return exit code zero even when the generated circuit contains errors.
-The motor-power port uses CH224K JLCPCB/LCSC part C970725 and USB-C receptacle
-C2765186. A 6.8 kohm CFG1 resistor selects the 9 V request. The CH224K VDD and
-VBUS-sense pins use 1 kohm and 10 kohm series resistors respectively, with 1 uF
-local VDD decoupling and 10 uF on the motor VBUS rail.
+`bun run verify` runs the complete build/check/test sequence. `check:built` gates the actual `tsci check` error count and the Gerber shorts result, since a zero CLI exit code alone is insufficient.
-## Power-trace routing
+The ground regression follows source traces and internal pin connections rather than matching labels or cached connectivity keys. It checks both GND declarations, 18 required ground pins, pour net assignments, and separation from the positive supply rails. Removing the new ground join makes the test fail. The same ground assertion runs against the fully routed board. It also checks both current-sense channels and all four motor-output connections and verifies that the four outputs remain distinct.
-The PCB is 50 x 49 mm with two copper layers and 0.3 mm via holes / 0.45 mm
-via pads. The board and reusable RP2040 subcircuit both explicitly use 10x
-autorouter effort. The lower sense resistors are spaced away from the driver
-outputs to leave a wider routing channel. Crystal signal traces retain their
-zero-via constraints.
+## Additional integration changes
-The board uses capacity autorouter Pipeline 7. Power-trace expansion is part of
-that pipeline, so the circuit does not import or run a separate post-routing
-solver. Connections whose requested width is materially larger than the board's
-ordinary routing width are expanded automatically after the multigraph route.
+- Route the fault-feedback network before the motor outputs, then route the remaining connections.
+- Route A− before the other motor outputs, then use a 0.20 mm routing clearance in the remaining motor-output phase to preserve trace width and pad clearance.
+- Define the B-channel current-sense escape with an explicit via; the original 0.10 mm DRC rules remain in force.
+- Enlarge the controller schematic sheet to 310 mm width so the latest renderer keeps the ground label and trace within its border.
+- Pass the existing `routingDisabled` option through to the board.
+- Enable TypeScript to resolve the existing JavaScript stock-audit module.
-After routing, the board generates top- and bottom-layer pours tied to one
-explicit board-level `GND` net. Both use 0.2 mm pad/trace clearance and remain
-0.3 mm inside the board edge. The board regression requires B-Rep pour islands
-on both layers to share the same ground net and remain covered by solder mask.
+## Validation status
-The board test independently reruns `@tscircuit/checks` routing validation on
-the completed Circuit JSON and requires zero routing or circuit errors. It also
-uses shape-aware pad geometry to reject any routed via inside an SMT pad.
+The final build has **0 embedded DRC errors, 0 independently rerun routing errors, 0 `tsci check` errors, no detected Gerber shorts, and 7 passing tests**. Warnings remain listed in the reports.
-The pad-clearance target is intentionally best-effort: dense package escapes
-may keep the board's legal 0.1 mm minimum when half-width spacing cannot fit.
-The regression separately budgets the remaining 0.15 mm preferred-clearance
-misses while still requiring the hard DRC suite to be completely clean.
+See `reports/validation.json` and `reports/verification.md` for the final build result and limitations. No changes have been published to tscircuit.com and no fabrication order has been placed.
-Also run `bun run check:shorts` before exporting fabrication files. This checks
-the Gerber-rendered copper, including pours and widened trace endpoints; these
-can reveal shorts that the normal routing checks miss. When changing router
-version or effort, invalidate the local routing cache before validation: the
-current cache key does not distinguish those settings.
-
-The current 50 x 49 mm build passes the embedded circuit checks, independently
-rerun routing DRC, and the all-layer Gerber short check. It is not yet
-fabrication-approved: the motor-output width regression still fails on A-.
-Its longest run below 0.85 mm is 7.28 mm (the test permits 5 mm), and its
-length-weighted average width is 0.781 mm (the test requires more than 0.8 mm).
-The test thresholds have not been relaxed. A four-layer trial was not retained:
-it still had clearance errors and generated partial-span vias instead of a
-standard through-via-only layout.
-
-## JLCPCB stock audit
-
-After building the current circuit, run `bun scripts/audit-jlcpcb-stock.mjs` to
-check public JLCPCB inventory and generate `reports/jlcpcb-stock-audit.csv` and
-`reports/jlcpcb-stock-audit.json`. The JSON includes the input Circuit JSON hash,
-per-part timestamps, displayed stock, and available order quantities. The command
-fails if a populated component lacks a code or a part is unverified/unavailable
-for even one board. Bare copper test pads are excluded; they are not purchased parts.
-
-The 2026-08-26 audit found codes for all 58 populated components (33 unique parts).
-32 unique parts had positive available order quantities. J1, C42377749, showed
-150 units but zero available stocked-order quantity; its product page offered
-preorder with a 28-piece minimum. Confirm procurement or validate a replacement
-before ordering assembly. No parts have been purchased or reserved.
-
-These reports are stock audits, not fabrication BOM/PnP files or design approval.
-Availability can change; check the actual board quantity, assembly attrition,
-minimums, and checkout availability. The existing
-`dist/rp2040-motor-controller-gerbers.zip` is outdated and must not be submitted.
-The electrical and mechanical issues from the fabrication review remain open.
+The original release's README and inventory audit are retained in `reports/` as historical records. They are not current stock checks or approval to fabricate this revision.