imrishabh18/rp2040-motor-controller

This circuit integrates a Raspberry Pi RP2040 microcontroller with multiple power management components, USB interface, drivers, sensors (including an AS5600 encoder), protection elements, and various passive and active components to facilitate device control, measurement, and communication.

Version
1.0.18
License
unset
Stars
0

tests/schematic-sheets.test.tsx

import { expect, test } from "bun:test";
import { convertCircuitJsonToStackedSchematicSheetsSvg } from "circuit-to-svg";
import { Circuit } from "tscircuit";
import Rp2040MotorController from "../index.circuit";

test("renders six readable schematic sheets with local component groups", async () => {
  const circuit = new Circuit({ platform: { pcbDisabled: true } });
  circuit.add(<Rp2040MotorController />);

  await circuit.renderUntilSettled();
  const circuitJson = circuit.getCircuitJson() as any[];
  const controllerSheetId = circuitJson.flatMap((element) =>
    element.type === "schematic_sheet" && element.name === "controller"
      ? [element.schematic_sheet_id]
      : [],
  )[0];
  if (!controllerSheetId) {
    throw new Error("Controller schematic sheet is missing");
  }
  const programmingSheetId = circuitJson.flatMap((element) =>
    element.type === "schematic_sheet" &&
    element.name === "controller_programming"
      ? [element.schematic_sheet_id]
      : [],
  )[0];
  if (!programmingSheetId) {
    throw new Error("Programming schematic sheet is missing");
  }
  const schematicUnitsPerMillimeter = 1.1 / 10.16;
  const componentFrameClearance = 0.5;
  const sourceComponentNames = new Map(
    circuitJson.flatMap((element) =>
      element.type === "source_component"
        ? [[element.source_component_id, element.name] as const]
        : [],
    ),
  );
  const schematicComponentsByName = new Map(
    circuitJson.flatMap((element) => {
      if (element.type !== "schematic_component") {
        return [];
      }
      const name = element.source_component_id
        ? sourceComponentNames.get(element.source_component_id)
        : undefined;
      return name ? [[name, element] as const] : [];
    }),
  );
  const componentsOutsideSheetFrames = circuitJson.flatMap((element) => {
    if (element.type !== "schematic_component") {
      return [];
    }
    const sheet = circuitJson.find(e => e.type === "schematic_sheet" && e.schematic_sheet_id === element.schematic_sheet_id);
    // The controller uses a 310 mm sheet; the other sheets use A4 width.
    const innerHalfWidth = ((sheet?.sheet_width ?? 297) / 2 - 5) * schematicUnitsPerMillimeter;
    const innerHalfHeight = ((sheet?.sheet_height ?? 210) / 2 - 5) * schematicUnitsPerMillimeter;
    const halfWidth = element.size.width / 2 + componentFrameClearance;
    const halfHeight = element.size.height / 2 + componentFrameClearance;
    const insideFrame =
      element.center.x - halfWidth >= -innerHalfWidth &&
      element.center.x + halfWidth <= innerHalfWidth &&
      element.center.y - halfHeight >= -innerHalfHeight &&
      element.center.y + halfHeight <= innerHalfHeight;
    return insideFrame
      ? []
      : [
          {
            sheetId: element.schematic_sheet_id,
            name: element.source_component_id
              ? sourceComponentNames.get(element.source_component_id)
              : undefined,
            center: element.center,
            size: element.size,
          },
        ];
  });

  expect(componentsOutsideSheetFrames).toEqual([]);
  expect(
    circuitJson.filter(
      (element) => element.type === "schematic_element_outside_sheet_warning",
    ),
  ).toEqual([]);

  const getSchematicComponent = (name: string) => {
    const component = schematicComponentsByName.get(name);
    if (!component) {
      throw new Error(`Missing schematic component ${name}`);
    }
    return component;
  };
  const distanceBetweenComponents = (firstName: string, secondName: string) => {
    const first = getSchematicComponent(firstName);
    const second = getSchematicComponent(secondName);
    return Math.hypot(
      first.center.x - second.center.x,
      first.center.y - second.center.y,
    );
  };

  expect(getSchematicComponent("U1").schematic_sheet_id).toBe(
    controllerSheetId,
  );
  for (const programmingComponentName of [
    "J_USB",
    "U2",
    "U3",
    "C_FLASH",
    "C_USB_VDD",
    "C_VBUS",
    "C_USB",
    "R_USB1",
    "R_USB2",
  ]) {
    expect(
      getSchematicComponent(programmingComponentName).schematic_sheet_id,
    ).toBe(programmingSheetId);
  }

  const iovddCapacitors = [
    "C_IOVDD1",
    "C_IOVDD2",
    "C_IOVDD3",
    "C_IOVDD4",
    "C_IOVDD5",
    "C_IOVDD6",
  ].map(getSchematicComponent);
  const iovddRows = new Map<number, number>();
  for (const capacitor of iovddCapacitors) {
    const rowKey = Math.round(capacitor.center.y * 1000);
    iovddRows.set(rowKey, (iovddRows.get(rowKey) ?? 0) + 1);
  }
  const rp2040 = getSchematicComponent("U1");
  const nearestIovddCapacitorRightEdge = Math.max(
    ...iovddCapacitors.map(
      (capacitor) => capacitor.center.x + capacitor.size.width / 2,
    ),
  );
  const rp2040LeftEdge = rp2040.center.x - rp2040.size.width / 2;

  expect([...iovddRows.values()]).toEqual([3, 3]);
  expect(rp2040LeftEdge - nearestIovddCapacitorRightEdge).toBeGreaterThan(0.75);

  // Regression: BOOT/RUN ground routing must not detour across the sheet.
  for (const trace of circuitJson.filter(e => e.type === "schematic_trace" &&
    e.schematic_sheet_id === controllerSheetId)) {
    const ys = trace.edges.flatMap((edge: any) => [edge.from.y, edge.to.y]);
    if (ys.length) expect(Math.max(...ys) - Math.min(...ys)).toBeLessThan(8);
  }

  const crystal = getSchematicComponent("Y1");
  expect(crystal.center.x).toBeCloseTo(-7.9, 1);
  expect(crystal.center.y).toBeCloseTo(-5, 1);

  const controllerSheet = circuitJson.find(
    (element) =>
      element.type === "schematic_sheet" &&
      element.schematic_sheet_id === controllerSheetId,
  );
  if (!controllerSheet) {
    throw new Error("Controller schematic sheet geometry is missing");
  }
  for (const switchName of ["SW_BOOT", "SW_RUN"]) {
    const schematicSwitch = getSchematicComponent(switchName);
    const switchTop =
      schematicSwitch.center.y + schematicSwitch.size.height / 2;
    expect(switchTop).toBeLessThan(controllerSheet.center.y - 0.5);
  }

  const localDecouplingDistances: Array<
    [capacitorName: string, targetName: string, maximumDistance: number]
  > = [
    ["C_CORE", "U1", 4.25],
    // Same-rail bypass capacitors now form a compact, labelled 2x2 bank.
    ["C_FLASH", "C_USB_VDD", 3.1],
    ["C_FLASH", "C_3V3", 2.6],
    ["C_USB_VDD", "C_USB", 2.6],
    ["C_XIN", "Y1", 2.5],
    ["C_XOUT", "Y1", 2.5],
    ["C_DVDD1", "U1", 6],
    ["C_DVDD2", "U1", 7],
    ["C_VM_HF", "DRIVER", 6],
    ["C_VM_BULK", "DRIVER", 7],
    ["C_MOTOR_BULK", "F_MOTOR_VBUS", 10],
    ["C_PD_VDD", "U_PD", 3.5],
  ];
  for (const [
    capacitorName,
    targetName,
    maximumDistance,
  ] of localDecouplingDistances) {
    expect(distanceBetweenComponents(capacitorName, targetName)).toBeLessThan(
      maximumDistance,
    );
  }

  expect(
    convertCircuitJsonToStackedSchematicSheetsSvg(circuitJson, {
      width: 1400,
      includeVersion: true,
      showErrorsInTextOverlay: true,
    }),
  ).toMatchSvgSnapshot(import.meta.path);
}, 15_000);