AnasSarkiz/usb-c-pd-brushed-motor
USB-C PD-powered motor controller with protected input, selectable 5/9/12 V buck supply, MCU qualification, PWM H-bridge drive, overvoltage sensing, regenerative energy-dump resistors, and safety interlocks.
- Version
- 0.0.1-a48-cloud-pipeline9-unrouted-c41d79a-305b1e7a
- License
- unset
- Stars
- 0
tests/motor-validation-calculations.test.ts
import { describe, expect, test } from "bun:test"
import {
buckInductorPeakA,
capacitorEnergyMarginJ,
motorTransientEnvelope,
reviewMotorContracts,
} from "../scripts/motor-validation-calculations"
describe("A7 voltage-aware PD policy engineering screen", () => {
test("5 A advertisements remain separate from the 3 A operating request", () => {
const review = reviewMotorContracts({
motorVoltageV: 12,
peakMotorCurrentA: 2.423,
sourceCapabilities: [{ voltageV: 20, currentA: 5 }],
})
expect(review.motorMustRemainInhibited).toBe(false)
expect(review.selectedContract?.requestedOperatingCurrentA).toBe(3)
expect(review.selectedContract?.expectedMaximumCurrentA).toBe(5)
for (const currentA of [5.01, Number.POSITIVE_INFINITY, Number.NaN])
expect(
reviewMotorContracts({
motorVoltageV: 12,
peakMotorCurrentA: 2.423,
sourceCapabilities: [{ voltageV: 20, currentA }],
}).motorMustRemainInhibited,
).toBe(true)
})
test("5 V uses 15 V; 9/12 V retain reserve with 20 V", () => {
for (const motorVoltageV of [5, 9, 12] as const) {
const review = reviewMotorContracts({
motorVoltageV,
peakMotorCurrentA: 2.4,
sourceCapabilities: [
{ voltageV: 15, currentA: 3 },
{ voltageV: 20, currentA: 3 },
],
})
expect(review.selectedContract?.voltageV).toBe(
motorVoltageV === 5 ? 15 : 20,
)
}
})
test("a low-power 20 V source fails despite enough nominal output watts", () => {
expect(
reviewMotorContracts({
motorVoltageV: 5,
peakMotorCurrentA: 2.4,
sourceCapabilities: [{ voltageV: 20, currentA: 2.25 }],
}).motorMustRemainInhibited,
).toBe(true)
})
test("5 V-only, native 12 V-only and unplugged sources inhibit", () => {
for (const sourceCapabilities of [
[],
[{ voltageV: 5, currentA: 3 }],
[{ voltageV: 12, currentA: 3 }],
[{ voltageV: 15, currentA: 2 }],
]) {
expect(
reviewMotorContracts({
motorVoltageV: 12,
peakMotorCurrentA: 2.4,
sourceCapabilities,
}).motorMustRemainInhibited,
).toBe(true)
}
})
test("20 V fallback fits a 9 V motor when 15 V is unavailable", () => {
expect(
reviewMotorContracts({
motorVoltageV: 9,
peakMotorCurrentA: 2.4,
sourceCapabilities: [{ voltageV: 20, currentA: 3 }],
}).selectedContract?.voltageV,
).toBe(20)
})
})
describe("Motor and converter stress screening", () => {
test("running reversal can double unrestricted startup current", () => {
const envelope = motorTransientEnvelope({
motorVoltageV: 12,
windingResistanceOhms: 1.5,
windingInductanceH: 150e-6,
backEmfV: 12,
currentRegulationDelayS: 3.3e-6,
currentLimitA: 2.4,
})
expect(envelope.unrestrictedStartupCurrentA).toBe(8)
expect(envelope.unrestrictedReversalCurrentA).toBe(16)
expect(envelope.possibleReversalOvershootA).toBeCloseTo(0.528)
expect(envelope.windingEnergyAtCurrentLimitJ).toBeCloseTo(0.000432, 6)
})
test("output capacitance cannot absorb the example rotor energy", () => {
const capacitorMarginJ = capacitorEnergyMarginJ({
effectiveCapacitanceF: 280e-6,
initialRailV: 12,
maximumRailV: 13.89,
})
const exampleRotorEnergyJ = 0.5 * 1e-5 * 300 ** 2
expect(capacitorMarginJ).toBeCloseTo(0.00685, 5)
expect(exampleRotorEnergyJ).toBeGreaterThan(capacitorMarginJ * 50)
})
test("screened inductor peaks remain below the 4.5 A minimum buck limit", () => {
for (const outputVoltageV of [5, 9, 12]) {
expect(
buckInductorPeakA({
inputVoltageV: 21,
outputVoltageV,
loadCurrentA: 2.4,
minimumInductanceH: 8.2e-6 * 0.8 * 0.7,
minimumSwitchingFrequencyHz: 600_000 * 0.9,
}),
).toBeLessThan(4.5)
}
})
})
import { railMonitorThresholdsV } from "../scripts/motor-validation-calculations"
describe("A7 motor-rail-powered analog clamp", () => {
test("reference stays biased and clamp has hysteresis in all selected modes", () => {
for (const branch of [undefined, 21_000, 12_000]) {
const result = railMonitorThresholdsV(branch)
expect(result.referenceBiasAtNominalA).toBeGreaterThan(0.001)
expect(result.referenceBiasResistorPowerAtTripW).toBeLessThan(0.1)
expect(result.ovTripV).toBeGreaterThan(result.ovReleaseV)
expect(result.ovReleaseV).toBeGreaterThan(result.nominalRailV)
expect(result.uvV).toBeLessThan(result.nominalRailV)
// Eight 2 W resistors still require a bounded pulse/repetition envelope.
if (branch === 12_000)
expect(result.instantaneousDumpPowerAtTripW).toBeGreaterThan(16)
}
})
})
import { dumpResistorEnvelope } from "../scripts/motor-validation-calculations"
describe("Four-branch regenerative resistor screen", () => {
test("current headroom is retained even at the lowest clamp release", () => {
const lowerRelease12V = railMonitorThresholdsV(12_000).ovReleaseV * 0.94
const bank = dumpResistorEnvelope({
railV: lowerRelease12V,
ambientC: 70,
pulseS: 0.1,
periodS: 1,
})
expect(bank.minimumSinkCurrentA).toBeGreaterThan(2.222)
expect(bank.maximumSinkCurrentA).toBeLessThan(4)
expect(bank.manufacturerSingleOverloadScreenPasses).toBe(true)
expect(bank.averageComponentScreenPasses).toBe(true)
})
test("a 15 V, 100 ms pulse fits the component overload screen", () => {
const bank = dumpResistorEnvelope({
railV: 15,
ambientC: 70,
pulseS: 0.1,
periodS: 1,
})
expect(bank.minimumSinkCurrentA).toBeGreaterThan(2.423)
expect(bank.worstResistorPowerW).toBeLessThan(bank.overloadPerResistorW)
expect(bank.worstResistorAveragePowerW).toBeLessThan(2)
expect(bank.worstResistorPulseEnergyJ).toBeLessThan(0.6)
expect(bank.repetitivePulseHardwareQualification).toBe("pending")
})
test("continuous braking and excessive pulse length are rejected", () => {
expect(
dumpResistorEnvelope({ railV: 15, ambientC: 70, pulseS: 1, periodS: 1 })
.averageComponentScreenPasses,
).toBe(false)
expect(
dumpResistorEnvelope({
railV: 15,
ambientC: 70,
pulseS: 2.001,
periodS: 30,
}).manufacturerSingleOverloadScreenPasses,
).toBe(false)
expect(() =>
dumpResistorEnvelope({
railV: 15,
ambientC: 155,
pulseS: 0.1,
periodS: 1,
}),
).toThrow()
})
})