mohan-bee/flight-controller-v2
A 64 mm STM32F405 flight-controller PCB with ICM-42688 IMU, SPI flash, USB-C/ESD, battery buck and 3.3 V regulation, four motor outputs, service I/O, and PETG standoff mounting hardware.
- Version
- 0.1.0
- License
- ISC
- Stars
- 0
validate.mjs
import { readFileSync } from "node:fs";
import assert from "node:assert/strict";
const file = process.argv[2] ?? "dist/index/circuit.json";
const circuit = JSON.parse(readFileSync(file, "utf8"));
const components = circuit.filter((item) => item.type === "source_component");
const nets = new Map(
circuit
.filter((item) => item.type === "source_net")
.map((item) => [item.source_net_id, item.name]),
);
const ports = circuit.filter((item) => item.type === "source_port");
function portNet(componentName, pinNumber) {
const component = components.find((item) => item.name === componentName);
assert(component, `Missing component ${componentName}`);
const port = ports.find(
(item) =>
item.source_component_id === component.source_component_id &&
item.pin_number === pinNumber,
);
assert(port, `Missing ${componentName} pin ${pinNumber}`);
const trace = circuit.find(
(item) =>
item.type === "source_trace" &&
item.connected_source_port_ids.includes(port.source_port_id),
);
assert(trace, `Unconnected ${componentName} pin ${pinNumber}`);
return nets.get(trace.connected_source_net_ids[0]);
}
for (const pin of [1, 19, 32, 48, 64]) assert.equal(portNet("U1", pin), "V3V3");
for (const pin of [12, 18, 63]) assert.equal(portNet("U1", pin), "GND");
assert.equal(portNet("U1", 13), "VDDA");
assert.equal(portNet("U1", 31), "VCAP1");
assert.equal(portNet("U1", 47), "VCAP2");
for (const [pin, net] of [
[20, "GYRO_CS"],
[21, "GYRO_SCK"],
[22, "GYRO_MISO"],
[23, "GYRO_MOSI"],
[24, "GYRO_INT"],
[29, "RX_TX"],
[30, "RX_RX"],
[44, "USB_DM_MCU"],
[45, "USB_DP_MCU"],
])
assert.equal(portNet("U1", pin), net);
for (let motor = 1; motor <= 4; motor++)
assert.equal(portNet("U1", 36 + motor), `M${motor}`);
for (const pin of [5, 8]) assert.equal(portNet("U2", pin), "GYRO_3V3");
for (const pin of [2, 3, 6, 7, 9, 10, 11])
assert.equal(portNet("U2", pin), "GND");
for (const [pin, net] of [
[1, "FLASH_CS"],
[2, "FLASH_MISO"],
[3, "V3V3"],
[4, "GND"],
[5, "FLASH_MOSI"],
[6, "FLASH_SCK"],
[7, "V3V3"],
[8, "V3V3"],
])
assert.equal(portNet("U6", pin), net);
const board = circuit.find((item) => item.type === "pcb_board");
const usb = components.find((item) => item.name === "J_USB");
const ground = circuit.find((item) => item.type === "source_net" && item.name === "GND");
const shieldPorts = ports.filter(
(port) => port.source_component_id === usb.source_component_id &&
port.port_hints.some((hint) => hint === "SHELL1" || hint === "SHELL2"),
);
assert.equal(shieldPorts.length, 4, "USB connector must expose all four shield pads");
for (const port of shieldPorts)
assert.equal(port.subcircuit_connectivity_map_key, ground.subcircuit_connectivity_map_key,
`USB shield pad ${port.name} must connect to ground`);
assert.equal(board.width, 64);
assert.equal(board.height, 64);
assert.equal(board.num_layers, 4);
const errors = circuit.filter((item) => item.type.endsWith("_error"));
assert.equal(errors.length, 0, errors.map((item) => item.message).join("\n"));
for (const component of circuit.filter((item) => item.type === "pcb_component"))
assert.equal(component.layer, "top", "All components must be on top");
const traces = circuit.filter((item) => item.type === "pcb_trace");
for (const trace of traces) {
for (const point of trace.route) {
assert(
Number.isFinite(point.x) && Number.isFinite(point.y),
"Non-finite route point",
);
if (point.route_type === "wire") {
assert(
Math.abs(point.x) + point.width / 2 <= 32 &&
Math.abs(point.y) + point.width / 2 <= 32,
"Copper extends beyond the rectangular board bounds",
);
}
}
}
console.log(
`${components.length} components; MCU power, IMU, flash, USB, CRSF and four motor nets verified; ${traces.length} PCB traces; no circuit-json errors.`,
);