AnasSarkiz/rp2040-bldc-servo-controller

RP2040-based motor-control board integrating a DRV8316 three-phase gate driver, AS5047P magnetic position sensor, USB-C power/communications, STUSB4500 USB-PD controller, SPI flash, and THVD1450 RS-485 transceiver.

Version
0.0.5
License
unset
Stars
0

scripts/power-envelope.py

"""Screen the USB PD power envelope. This is not a converter simulation."""

import json
from pathlib import Path


def screen_operating_point(operating_point, assumptions):
    input_voltage = operating_point["input_voltage_v"]
    output_current = operating_point["output_current_a"]
    output_voltage = assumptions["output_voltage_v"]
    output_power = output_voltage * output_current
    boost_input_current = output_power / (assumptions["boost_efficiency_assumed"] * input_voltage)
    total_input_current = boost_input_current + assumptions["other_load_power_assumed_w"] / input_voltage
    current_limit_low = assumptions["input_current_limit_nominal_a"] * (1 - assumptions["input_current_limit_tolerance_assumed"])
    duty_cycle = 1 - input_voltage / (output_voltage + assumptions["diode_drop_assumed_v"])
    ripple_current = input_voltage * duty_cycle / (assumptions["inductance_min_assumed_h"] * assumptions["switching_frequency_screen_hz"])
    peak_inductor_current = boost_input_current + ripple_current / 2
    input_limited_power = (input_voltage * current_limit_low - assumptions["other_load_power_assumed_w"]) * assumptions["boost_efficiency_assumed"]
    switch_limited_power = (assumptions["switch_current_limit_datasheet_min_a"] - ripple_current / 2) * assumptions["boost_efficiency_assumed"] * input_voltage
    return {
        **operating_point,
        "output_power_w": output_power,
        "total_input_current_a": total_input_current,
        "input_current_budget_margin_a": current_limit_low - total_input_current,
        "inductor_ripple_peak_to_peak_a": ripple_current,
        "peak_inductor_current_a": peak_inductor_current,
        "switch_current_limit_margin_a": assumptions["switch_current_limit_datasheet_min_a"] - peak_inductor_current,
        "screened_max_output_current_a": min(input_limited_power, switch_limited_power) / output_voltage,
        "input_budget_screen_pass": total_input_current <= current_limit_low,
        "switch_current_screen_pass": peak_inductor_current <= assumptions["switch_current_limit_datasheet_min_a"],
    }


def main():
    assumptions = {
        "output_voltage_v": 24,
        "negotiated_current_required_a": 5,
        "input_current_limit_nominal_a": 4.5,
        "input_current_limit_tolerance_assumed": 0.05,
        "other_load_power_assumed_w": 2,
        "boost_efficiency_assumed": 0.90,
        "diode_drop_assumed_v": 0.5,
        "inductance_min_assumed_h": 8e-6,
        "switching_frequency_screen_hz": 460000,
        "switch_current_limit_datasheet_min_a": 5.25,
        "switch_current_limit_datasheet_max_a": 7.75,
    }
    current_limit_low = assumptions["input_current_limit_nominal_a"] * (1 - assumptions["input_current_limit_tolerance_assumed"])
    current_limit_high = assumptions["input_current_limit_nominal_a"] * (1 + assumptions["input_current_limit_tolerance_assumed"])
    assert current_limit_high < assumptions["negotiated_current_required_a"]
    cases = [screen_operating_point({"input_voltage_v": input_voltage, "output_current_a": output_current}, assumptions)
             for input_voltage in [18, 19, 20] for output_current in [1.8, 2, 3]]
    at_18v_3a = next(case for case in cases if case["input_voltage_v"] == 18 and case["output_current_a"] == 3)
    at_18v_2a = next(case for case in cases if case["input_voltage_v"] == 18 and case["output_current_a"] == 2)
    assert not at_18v_3a["input_budget_screen_pass"]
    assert at_18v_2a["input_budget_screen_pass"] and at_18v_2a["switch_current_screen_pass"]
    minimum_input_for_3a = (24 * 3 / assumptions["boost_efficiency_assumed"] + assumptions["other_load_power_assumed_w"]) / current_limit_low
    result = {
        "screening_only": True,
        "candidate": "TPS55340RTER / C169167; imported but blocked by NC metadata defect I-018",
        "source": "TI TPS55340 SLVSBD4E sections 6.5 and 8.2.1; actual parts and operating bounds remain unselected",
        "assumptions": assumptions,
        "assumption_limits": [
            "Efficiency, overhead, board-input range, current-limit tolerance, inductance and diode drop are screening assumptions, not validated component limits.",
            "460 kHz is the datasheet lower frequency at RFREQ=80 kohm; no actual frequency resistor/tolerance is selected.",
            "5.25/7.75 A are internal switch current-limit bounds, not output-current ratings.",
            "Input current limiting, output-short disconnection, hardware interlocks and regenerative braking remain undesigned.",
            "Only a verified 20 V / 5 A contract may enable the motor; 5/9/15 V and inadequate current contracts keep it disabled.",
        ],
        "input_limit_low_a": current_limit_low,
        "input_limit_high_a": current_limit_high,
        "minimum_input_for_24v_3a_under_assumptions_v": minimum_input_for_3a,
        "capacitor_for_1a_500us_0p5v_sag_f": 1 * 500e-6 / 0.5,
        "energy_in_1mf_from_24v_to_28v_j": 0.5 * 1e-3 * (28**2 - 24**2),
        "cases": cases,
        "motor_startup_verified": False,
        "thermal_rating_verified": False,
        "power_design_complete": False,
    }
    Path("evidence/tooling-20261026/power-envelope.json").write_text(json.dumps(result, indent=2) + "\n")
    print(f"Input current limiter assumed range: {current_limit_low:.3f}–{current_limit_high:.3f} A")
    print(f"24 V / 3 A requires board input >= {minimum_input_for_3a:.3f} V under these assumptions")
    print(f"18 V input screening ceiling: {at_18v_3a['screened_max_output_current_a']:.3f} A at 24 V")
    print("Startup, thermal performance and completed protection: NOT VERIFIED")


if __name__ == "__main__":
    main()