mohan-bee/gimbal-one-budget-pcb

A 4-layer STM32-based gimbal controller PCB with an onboard LSM6DS3TR-C IMU, three DRV8313 3-phase motor drivers, protected 3S battery input, 3.3 V buck regulation, and SWD/UART control interfaces.

Version
0.1.0
License
unset
Stars
0

verify.ts

PCBPCB preview for verify.ts
SchematicSchematic preview for verify.ts
import assert from "node:assert/strict"
import { readFileSync, writeFileSync } from "node:fs"

const file = process.argv.slice(2).find(arg=>!arg.startsWith("--")) ?? "dist/index/circuit.json"
const circuit = JSON.parse(readFileSync(file, "utf8")) as Record<string, any>[]
const components = circuit.filter(x => x.type === "source_component")
const byName = new Map(components.map(x => [x.name, x]))
const nets = new Map(circuit.filter(x => x.type === "source_net").map(x => [x.source_net_id, x.name]))
function portNet(name: string, pin: number) {
  const component = byName.get(name)
  assert.ok(component, `Missing ${name}`)
  const port = circuit.find(x => x.type === "source_port" && x.source_component_id === component.source_component_id && Number(x.pin_number) === pin)
  assert.ok(port, `Missing ${name}.${pin}`)
  const connected = circuit.filter(x => x.type === "source_trace" && x.connected_source_port_ids?.includes(port.source_port_id)).flatMap(x => x.connected_source_net_ids ?? [])
  return [...new Set(connected.map(id => nets.get(id)).filter(Boolean))]
}
function expect(name: string, pin: number, net: string) { assert.deepEqual(portNet(name, pin), [net], `${name}.${pin}`) }
assert.equal(circuit.filter(x => x.type === "pcb_board").length, 1, "Exactly one installed PCB")
for (const [p,n] of Object.entries({1:"V3V3",19:"GND",20:"VDDA",21:"VDDA",23:"GND",24:"V3V3",35:"GND",36:"V3V3",47:"GND",48:"V3V3"})) expect("U_MCU",Number(p),n)
for (const [axis,pwm,en,fault] of [["PITCH",[30,31,32],15,11],["ROLL",[8,9,10],33,12],["YAW",[41,42,16],34,13]] as const) {
  pwm.forEach((p,i)=>expect("U_MCU",p,`${axis}_PWM${i+1}`))
  expect("U_MCU",en,`${axis}_ENABLE_CMD`);expect("U_MCU",fault,`${axis}_FAULT`)
  for(const p of [4,11]) expect(`U_${axis}`,p,"VM")
  for(const p of [6,7,10,14,20,28,29]) expect(`U_${axis}`,p,"GND")
  for(const p of [17,22,24,26]) expect(`U_${axis}`,p,`${axis}_ENABLE`)
  expect(`U_${axis}`,16,"DRIVE_RESET");expect(`U_${axis}`,18,`${axis}_FAULT`)
  ;[5,8,9].forEach((p,i)=>{expect(`U_${axis}`,p,`${axis}_${["U","V","W"][i]}`);expect(`J_${axis}`,i+1,`${axis}_${["U","V","W"][i]}`)})
  expect(`U_${axis}`,15,`${axis}_V3P3`)
}
for(const [p,n] of Object.entries({1:"IMU_MISO",4:"IMU_INT",5:"IMU_3V3",6:"GND",7:"GND",8:"IMU_3V3",12:"IMU_CS",13:"IMU_SCK",14:"IMU_MOSI"}))expect("U_IMU",Number(p),n)
for(const p of [9,10,11]) assert.deepEqual(portNet("U_IMU",p),[],`LSM unused pin ${p} must float`)
assert.equal(byName.get("U_IMU")?.manufacturer_part_number,"LSM6DS3TR-C")
for(const [p,n] of Object.entries({26:"IMU_CS",27:"IMU_SCK",28:"IMU_MISO",29:"IMU_MOSI",18:"IMU_INT",14:"VBAT_ADC",37:"SWDIO",38:"SWCLK"}))expect("U_MCU",Number(p),n)
for(const p of [1,2,3])expect("Q1",p,"VM")
for(const p of [5,6,7,8])expect("Q1",p,"BAT_FUSED")
expect("Q1",4,"RP_GATE")
expect("U_BUCK",1,"V3V3");expect("U_BUCK",2,"VM");expect("U_BUCK",3,"VM");expect("U_BUCK",4,"GND");expect("U_BUCK",5,"BUCK_SW");expect("U_BUCK",6,"BUCK_BST")
expect("J_STOP",1,"DRIVE_RESET");expect("J_STOP",2,"GND")
const refs = JSON.parse(readFileSync("refdes.json","utf8")) as Record<string,string>
for(const name of Object.values(refs))assert.ok(byName.has(name),`Missing ${name}`)
const errors = circuit.filter(x=>x.type.endsWith("_error"))
const report={file,sourceTopology:"PASS",componentCount:components.length,pcbCount:1,pcbTraceCount:circuit.filter(x=>x.type==="pcb_trace").length,errors:errors.reduce((a,x)=>(a[x.type]=(a[x.type]??0)+1,a),{} as Record<string,number>)}
writeFileSync("validation-summary.json",JSON.stringify(report,null,2))
console.log(JSON.stringify(report,null,2))
// A topology pass never masks routing/placement failures in the release check.
if(process.argv.includes("--drc-clean"))assert.equal(errors.length,0,"Circuit JSON contains errors")