imrishabh18/rp2040-motor-controller

A library of PCB component definitions covering MOSFETs, motor drivers, USB-PD control, sensors, current monitors, transistors, diodes, resistors, speakers, connectors, and mirrored bottom-side logos, with pin mappings, footprints, and 3D models.

Version
1.0.55
License
unset
Stars
5

tests/helpers/assertPdBusVoltageMonitor.ts

import type { CircuitJson } from "circuit-json";

/** Verify the raw PD supply can be read without exposing RP2040 GPIOs to VBUS. */
export function assertPdBusVoltageMonitor(json: CircuitJson) {
  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 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 component = (name: string) => {
    const result = json.find(element => element.type === "source_component" && element.name === name);
    if (result?.type !== "source_component") throw new Error(`Missing ${name}`);
    return result;
  };
  const pin = (name: string, number: number) => {
    const part = component(name);
    const port = json.find(element => element.type === "source_port" &&
      element.source_component_id === part.source_component_id && element.pin_number === number);
    if (port?.type !== "source_port") throw new Error(`Missing ${name} physical pin ${number}`);
    return port;
  };
  const net = (name: string, number: number) => root(pin(name, number).source_port_id);
  const same = (a: [string, number], b: [string, number]) => {
    if (net(...a) !== net(...b)) throw new Error(`Disconnected ${a.join(".")} / ${b.join(".")}`);
  };
  const monitor = component("U_PD_MON");
  if (monitor.manufacturer_part_number !== "INA226AIDGSR" ||
    monitor.supplier_part_numbers?.jlcpcb?.[0] !== "C49851") {
    throw new Error("PD monitor must use the imported JLCPCB INA226 footprint");
  }
  const bypass = component("C_PD_MON");
  if (!("capacitance" in bypass) || bypass.capacitance !== 1e-7) throw new Error("Missing 100 nF INA226 bypass");
  for (const [a, b] of [
    [["U_PD_MON", 8], ["J_MOTOR_USB", 6]], // VBUS before fuse and blocking diode
    [["U_PD_MON", 10], ["U_PD_MON", 8]], // no shunt on unused current input
    [["U_PD_MON", 9], ["U_PD_MON", 8]],
    [["U_PD_MON", 6], ["L_AVDD", 1]],
    [["U_PD_MON", 6], ["C_PD_MON", 1]],
    [["U_PD_MON", 7], ["C_PD_MON", 2]],
    [["U_PD_MON", 1], ["U_PD_MON", 7]], // 7-bit I2C address 0x40
    [["U_PD_MON", 2], ["U_PD_MON", 7]],
    [["U_PD_MON", 4], ["U_TEMP", 6]],
    [["U_PD_MON", 5], ["U_TEMP", 1]],
    [["U1", 27], ["R_BUZZER_GATE", 1]], // GPIO16 already drives the buzzer
    [["R_BUZZER_GATE", 2], ["Q_BUZZER", 1]],
  ] as [string, number][][]) same(a!, b!);
  if (!pin("U_PD_MON", 3).do_not_connect) throw new Error("INA226 ALERT must be explicitly unconnected");
  if (new Set([
    net("U_PD_MON", 8), net("U_PD_MON", 6), net("U_PD_MON", 7),
    net("U_PD_MON", 4), net("U_PD_MON", 5), net("J_USB", 18),
    net("D_MOTOR_BLOCK", 1),
  ]).size !== 7) throw new Error("PD voltage monitor shorts power or I2C domains");
  const physical = json.find(element => element.type === "pcb_component" &&
    element.source_component_id === monitor.source_component_id);
  if (physical?.type === "pcb_component" && physical.layer !== "top") {
    throw new Error("PD monitor must be populated on the top for single-sided assembly");
  }
  if (physical?.type === "pcb_component") {
    const left = physical.center.x - physical.width / 2;
    const right = physical.center.x + physical.width / 2;
    const bottom = physical.center.y - physical.height / 2;
    const top = physical.center.y + physical.height / 2;
    for (const hole of json.filter(element => element.type === "pcb_hole" || element.type === "pcb_plated_hole")) {
      const x = hole.x, y = hole.y;
      const dimensions = hole as typeof hole & {
        outer_width?: number; outer_height?: number; outer_diameter?: number;
        hole_width?: number; hole_height?: number; hole_diameter?: number;
      };
      const halfWidth = (dimensions.outer_width ?? dimensions.outer_diameter ?? dimensions.hole_width ?? dimensions.hole_diameter ?? 0) / 2;
      const halfHeight = (dimensions.outer_height ?? dimensions.outer_diameter ?? dimensions.hole_height ?? dimensions.hole_diameter ?? 0) / 2;
      if (x + halfWidth + 0.2 > left && x - halfWidth - 0.2 < right &&
        y + halfHeight + 0.2 > bottom && y - halfHeight - 0.2 < top) {
        throw new Error(`PD monitor body conflicts with through-hole geometry at (${x}, ${y})`);
      }
    }
  }
  return { status: "pass", monitorPart: "C49851", rawPdBus: true, i2cAddress: "0x40",
    i2cGpios: [26, 27], buzzerGpio: 16, motorEnableGpio: 22,
    requiresDataUsbAndApplicationFirmware: true };
}