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

scripts/electrical-calculations.ts

export interface PdCapability {
  voltageV: number
  currentA: number
}

// A7 host policy: prefer the lowest adequate fixed-voltage contract.
// Conservative engineering screen; hardware efficiency/thermal measurements pending.
export const inputBudgetReserveFraction = 0.05

export function chooseMotorContract(
  sourceCapabilities: PdCapability[],
  selection: { motorVoltageV: 5 | 9 | 12; peakMotorCurrentA?: number },
) {
  const { motorVoltageV, peakMotorCurrentA = 2.423 } = selection
  // 1% initial tolerance plus 100 ppm/K over 100 K, added conservatively.
  const minimumLimitA = ((18_000 / 6_980) * 0.9) / 1.02
  const maximumLimitA = ((18_000 / 6_980) * 1.1) / 0.98
  const adequate = sourceCapabilities
    .filter(
      (source) =>
        (source.voltageV === 15 || source.voltageV === 20) &&
        source.currentA >= 3 &&
        source.currentA <= 5 &&
        source.currentA >= maximumLimitA + 1 / (source.voltageV * 0.95) &&
        motorInputCurrentA({
          motorVoltageV,
          motorCurrentA: peakMotorCurrentA,
          contractVoltageV: source.voltageV,
        }) *
          (1 + inputBudgetReserveFraction) <=
          minimumLimitA,
    )
    .sort((a, b) => a.voltageV - b.voltageV)[0]
  return adequate ? { voltageV: adequate.voltageV, currentA: 3 } : null
}

export function motorInputCurrentA({
  motorVoltageV,
  motorCurrentA,
  contractVoltageV,
}: {
  motorVoltageV: number
  motorCurrentA: number
  contractVoltageV: number
}) {
  const minimumSourceVoltageV = contractVoltageV * 0.95
  const assumedDiodeDropV = 0.55
  const assumedBridgeResistanceOhms = 0.36
  const assumedBuckEfficiency = 0.85
  const assumedAuxiliaryPowerW = 1
  const maximumMotorRailV = motorVoltageV * 1.05
  const bleederPowerW = maximumMotorRailV ** 2 / 940
  const bridgeLossW = motorCurrentA ** 2 * assumedBridgeResistanceOhms
  return (
    (maximumMotorRailV * motorCurrentA + bridgeLossW + bleederPowerW) /
      assumedBuckEfficiency /
      (minimumSourceVoltageV - assumedDiodeDropV) +
    assumedAuxiliaryPowerW / minimumSourceVoltageV
  )
}

export function regulatedMotorVoltageV(parallelBranchOhms?: number) {
  const feedbackTopOhms = 100_000 + 5_100
  const feedbackBottomOhms = parallelBranchOhms
    ? 1 / (1 / 20_000 + 1 / parallelBranchOhms)
    : 20_000
  return 0.8 * (1 + feedbackTopOhms / feedbackBottomOhms)
}

export function pwmTiming({
  fraction,
  potResistanceOhms = 10_000,
  timingCapacitanceF = 5.6e-9,
}: {
  fraction: number
  potResistanceOhms?: number
  timingCapacitanceF?: number
}) {
  // Analytic estimate including diode and discharge transistor drops.
  // These nominal drops are assumptions; no simulated/bench guarantee.
  const supplyV = 3.3
  const diodeDropV = 0.2
  const dischargeDropV = 0.1
  const lowerThresholdV = supplyV / 3
  const upperThresholdV = (supplyV * 2) / 3
  const chargeTimeS =
    timingCapacitanceF *
    (330 + fraction * potResistanceOhms) *
    Math.log(
      (supplyV - diodeDropV - lowerThresholdV) /
        (supplyV - diodeDropV - upperThresholdV),
    )
  const dischargeTimeS =
    timingCapacitanceF *
    (330 + (1 - fraction) * potResistanceOhms) *
    Math.log(
      (upperThresholdV - diodeDropV - dischargeDropV) /
        (lowerThresholdV - diodeDropV - dischargeDropV),
    )
  return {
    frequencyHz: 1 / (chargeTimeS + dischargeTimeS),
    dutyFraction: chargeTimeS / (chargeTimeS + dischargeTimeS),
  }
}