imrishabh18/rp2040-motor-controller
An RP2040-based NEMA17 stepper-motor controller with USB-C programming and PD motor-power negotiation, DRV8825 dual H-bridge drive, current/temperature telemetry, protected power filtering, status RGB LED, buzzer alarm, and optional magnetic encoder.
- Version
- 1.0.41
- License
- unset
- Stars
- 3
tests/feedback.test.tsx
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { Circuit } from "tscircuit";
import Board from "../index.circuit";
test("optional bottom encoder, buzzer switching/flyback and low-profile motor capacitor", async () => {
let json: any[];
if (process.env.CIRCUIT_JSON_PATH) json = JSON.parse(readFileSync(process.env.CIRCUIT_JSON_PATH, "utf8"));
else {
const circuit = new Circuit({ platform: { routingDisabled: true } });
circuit.add(<Board routingDisabled />);
await circuit.renderUntilSettled();
json = circuit.getCircuitJson();
}
const bottomComponents = json.filter(e => e.type === "pcb_component" && e.layer === "bottom" &&
json.some(s => s.type === "source_component" && s.source_component_id === e.source_component_id));
expect(bottomComponents).toHaveLength(3);
const encoder = json.find(e => e.type === "source_component" && e.name === "U_ENCODER");
const bottomNames = bottomComponents.map(c => json.find(s => s.type === "source_component" && s.source_component_id === c.source_component_id).name);
expect(new Set(bottomNames)).toEqual(new Set(["U_ENCODER", "C_ENCODER", "C_ENCODER_BULK"]));
expect(bottomComponents.every(c => c.do_not_place)).toBe(true);
expect(bottomComponents.find(c => c.source_component_id === encoder.source_component_id).center).toEqual({ x: 0, y: 0 });
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 e of json) {
const ids = e.type === "source_trace" ? [...e.connected_source_port_ids, ...e.connected_source_net_ids]
: e.type === "source_component_internal_connection" ? e.source_port_ids : [];
for (const id of ids.slice(1)) parent.set(root(id), root(ids[0]));
}
const component = (name: string) => json.find(e => e.type === "source_component" && e.name === name)!;
const pin = (name: string, label: string) => {
const port = json.find(e => e.type === "source_port" && e.source_component_id === component(name).source_component_id &&
(e.name === label || e.port_hints?.includes(label)));
if (!port) throw new Error(`Missing ${name}.${label}`);
return root(port.source_port_id);
};
// Physical package pins, independently checked against the imported datasheets.
const groups = [
[["U1", "GPIO0"], ["U_ENCODER", "pin6"], ["R_ENC_SDA", "pin1"]],
[["U1", "GPIO1"], ["U_ENCODER", "pin7"], ["R_ENC_SCL", "pin1"]],
[["U_ENCODER", "pin1"], ["U_ENCODER", "pin2"], ["C_ENCODER", "pin1"], ["C_ENCODER_BULK", "pin1"], ["R_ENC_SDA", "pin2"], ["R_ENC_SCL", "pin2"], ["L_AVDD", "pin1"], ["BZ1", "pin1"], ["D_BUZZER", "cathode"], ["C_BUZZER", "pin1"]],
[["U_ENCODER", "pin4"], ["U_ENCODER", "pin8"], ["C_ENCODER", "pin2"], ["C_ENCODER_BULK", "pin2"], ["U1", "GND"], ["Q_BUZZER", "pin2"],
["R_BUZZER_PD", "pin2"], ["C_BUZZER", "pin2"], ["C_MOTOR_BULK", "pin2"]],
[["U1", "GPIO16"], ["R_BUZZER_GATE", "pin1"]],
[["R_BUZZER_GATE", "pin2"], ["Q_BUZZER", "pin1"], ["R_BUZZER_PD", "pin1"]],
[["Q_BUZZER", "pin3"], ["BZ1", "pin2"], ["D_BUZZER", "anode"]],
[["D_MOTOR_BLOCK", "cathode"], ["C_MOTOR_BULK", "pin1"]],
];
for (const group of groups) for (const p of group.slice(1)) expect(pin(p[0]!, p[1]!)).toBe(pin(group[0]![0]!, group[0]![1]!));
expect(new Set(groups.map(g => pin(g[0]![0]!, g[0]![1]!))).size).toBe(groups.length);
expect(component("R_BUZZER_GATE").resistance).toBe(100);
expect(component("R_BUZZER_PD").resistance).toBe(100000);
for (const [name, mpn] of [["BZ1", "HYG-8503A"], ["C_MOTOR_BULK", "TCJD226M050R0090E"]]) {
expect(component(name!).manufacturer_part_number).toBe(mpn);
const pcb = json.find(e => e.type === "pcb_component" && e.source_component_id === component(name!).source_component_id)!;
expect(pcb.layer).toBe("top");
const cad = json.find(e => e.type === "cad_component" && e.pcb_component_id === pcb.pcb_component_id)!;
expect(cad.model_obj_url).toContain("modelcdn.tscircuit.com");
if (name === "C_MOTOR_BULK") {
expect(component(name).capacitance).toBeCloseTo(22e-6, 8);
expect(component(name).supplier_part_numbers.jlcpcb).toEqual(["C5981428"]);
expect(component(name).max_voltage_rating).toBe(50);
expect(component(name).are_pins_interchangeable).toBe(false);
const positive = json.find(e => e.type === "source_port" && e.source_component_id === component(name).source_component_id && e.name === "pin1");
const positivePad = json.find(e => e.type === "pcb_port" && e.source_port_id === positive.source_port_id);
// Supplier D-case anode is on the lower pad at this 90° rotation.
expect(positivePad.x).toBeCloseTo(-18, 6);
expect(positivePad.y).toBeCloseTo(-4.852419, 6);
expect(json.filter(e => e.type === "pcb_smtpad" && e.pcb_component_id === pcb.pcb_component_id)).toHaveLength(2);
expect(json.filter(e => e.type === "pcb_plated_hole" && e.pcb_component_id === pcb.pcb_component_id)).toHaveLength(0);
}
}
}, 60_000);
