imrishabh18/rp2040-motor-controller
This circuit module provides a microcontroller-based NEMA17 stepper motor driver with integrated USB-C Power Delivery, temperature sensing, and safety interlocks.
- Version
- 1.0.16
- License
- unset
- Stars
- 0
tests/helpers/assertThermalProtection.ts
import type { CircuitJson } from "circuit-json";
/** Recompute electrical connectivity; never trust cached connectivity keys. */
export function assertThermalProtection(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 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) => {
const e = json.find(e => e.type === "source_component" && e.name === name);
if (e?.type !== "source_component") throw new Error(`Missing ${name}`);
return e;
};
const pin = (name: string, label: string) => {
const c = component(name);
const p = json.find(e => e.type === "source_port" && e.source_component_id === c.source_component_id &&
(e.name === label || e.port_hints?.includes(label)));
if (p?.type !== "source_port") throw new Error(`Missing ${name}.${label}`);
return root(p.source_port_id);
};
const same = (a: string[], b: string[]) => {
if (pin(a[0]!, a[1]!) !== pin(b[0]!, b[1]!)) throw new Error(`Disconnected ${a.join('.')} / ${b.join('.')}`);
};
for (const [part, mpn] of [["U_TEMP", "TMP102AIDRLR"], ["U_THERM_GATE", "SN74LVC1G08DBVR"]]) {
if (component(part!).manufacturer_part_number !== mpn) throw new Error(`Wrong ${part} part`);
}
// Physical package pin numbers, independently checked against TI datasheets.
const groups = [
// Preserve the existing motor firmware pinout through the MCU escape vias.
[["U1", "GPIO18"], ["DRIVER", "pin16"]],
[["U1", "GPIO19"], ["DRIVER", "pin15"]],
[["U1", "GPIO20"], ["DRIVER", "pin9"]],
[["U1", "GPIO21"], ["DRIVER", "pin10"]],
[["U_TEMP", "pin6"], ["U1", "GPIO26_ADC0"], ["R_TEMP_SDA", "pin1"]],
[["U_TEMP", "pin1"], ["U1", "GPIO27_ADC1"], ["R_TEMP_SCL", "pin1"]],
[["U_TEMP", "pin3"], ["U1", "GPIO24"], ["U_THERM_GATE", "pin2"], ["R_TEMP_ALERT", "pin1"]],
[["U_THERM_GATE", "pin1"], ["Q_PD_ENABLE", "drain"], ["R_ENABLE_PD", "pin1"]],
[["U_THERM_GATE", "pin4"], ["DRIVER", "pin1"], ["R_SLEEP_PD", "pin1"]],
[["U1", "GPIO22"], ["Q_PD_ENABLE", "source"], ["R_MCU_ENABLE_PD", "pin1"]],
[["Q_PD_ENABLE", "gate"], ["U_PD", "PG"]],
[["U_TEMP", "pin5"], ["U_THERM_GATE", "pin5"], ["L_AVDD", "pin1"],
...["C_TEMP", "C_THERM_GATE"].map(n => [n, "pin1"]),
...["R_TEMP_SDA", "R_TEMP_SCL", "R_TEMP_ALERT"].map(n => [n, "pin2"])],
[["DRIVER", "GND2"], ["U_TEMP", "pin2"], ["U_TEMP", "pin4"], ["U_THERM_GATE", "pin3"],
...["C_TEMP", "C_THERM_GATE", "R_ENABLE_PD", "R_MCU_ENABLE_PD"].map(n => [n, "pin2"])],
];
for (const group of groups) for (const other of group.slice(1)) same(group[0]!, other);
// The gate and transistor must not be bypassed by a stray old trace.
const separated = groups.map(group => pin(group[0]![0]!, group[0]![1]!));
if (new Set(separated).size !== separated.length) throw new Error("Thermal interlock bypass or signal/power short");
for (const [name, ohms] of [["R_TEMP_SDA", 4700], ["R_TEMP_SCL", 4700], ["R_TEMP_ALERT", 10000],
["R_ENABLE_PD", 100000], ["R_MCU_ENABLE_PD", 100000]] as const) {
const c = component(name);
if (!("resistance" in c) || c.resistance !== ohms) throw new Error(`Wrong ${name} resistance`);
}
const pcbFor = (name: string) => json.find(e => e.type === "pcb_component" &&
e.source_component_id === component(name).source_component_id);
const driver = pcbFor("DRIVER"), sensor = pcbFor("U_TEMP");
let sensorDistanceMm: number | null = null;
if (driver?.type === "pcb_component" && sensor?.type === "pcb_component") {
sensorDistanceMm = Math.hypot(driver.center.x - sensor.center.x, driver.center.y - sensor.center.y);
if (sensorDistanceMm > 5 || driver.layer !== sensor.layer) throw new Error("Temperature sensor too far from driver");
}
return { i2cAddress: "0x48", sdaGpio: 26, sclGpio: 27, alertGpio: 24, enableGpio: 22, sensorDistanceMm,
checkedElectricalGroups: groups.length, hardwareInterlock: "PD-gated MCU enable AND TMP102 ALERT", status: "pass" };
}