MustafaMulla29/rp2040-bldc-motor-controller-new

A USB‑C PD–powered RP2040 BLDC motor controller PCB with 3.3 V logic, MCT8329A sensorless commutation, three MOSFET half-bridges, protected power switching, USB data, controls, and motor connectors.

Version
0.3.82
License
unset
Stars
0

firmware/pd_profile.py

"""Selectable 5/9/15/20 V motor supply; nonvolatile startup remains 9 V for the STUSB4500.

NVM field locations: SparkFun STUSB4500 library (manufacturer of a board using
this IC), and ST's STUSB4500 NVM_Flasher. Preserve all unrelated factory bits.
https://github.com/sparkfun/SparkFun_STUSB4500_Arduino_Library
https://github.com/usb-c/STUSB4500/tree/master/NVM_Flasher
"""
from power_policy import fixed_sink_pdo, DEFAULT_MV, CONTRACT_MA

def profiles(selected_mv=DEFAULT_MV):
    # Mandatory PDO1 is the low-current fallback. PDO2 is the selected motor
    # contract even at 5 V. Inactive PDO3 mirrors PDO2 to avoid a latent profile.
    motor = fixed_sink_pdo(selected_mv, CONTRACT_MA)
    return (fixed_sink_pdo(5000, 500, True), motor, motor)

PDOS = profiles()  # Backwards-compatible name for the 9 V cold-start image.


def nvm_9v_image(original):
    if len(original) != 40:
        raise ValueError("STUSB4500 requires five 8-byte NVM sectors")
    result = bytearray(original)
    # Sector 3 byte 2: 0.5 A PDO1, two active PDOs, USB data capable.
    result[26] = 0x15  # also clear the externally/unconstrained-powered flag
    result[28] = (result[28] & 0xF0) | 7  # 2 A PDO2 (LUT code 7)
    result[29] = (result[29] & 0x0F) | 0x70  # 2 A inactive PDO3
    # 9 V = 180 units of 50 mV. Both flexible voltage fields are 10 bits.
    result[32] &= 0x3F
    result[33] = 45
    result[34] = 180
    result[35] &= 0xFC
    # ST NVM register map: E4[6:5]=10 means POWER_OK2 reports PDO2.
    result[36] = (result[36] & ~0x60) | 0x40
    # E6[4]=1: VBUS_EN_SNK only with an explicit PDO2/PDO3 contract.
    # Despite POWER_ONLY_ABOVE_5V naming, DS12499 Rev 8 Table 6 specifies PDO index. E6[3]=0:
    # request our programmed current. Logic is supplied BEFORE this switch.
    result[38] = (result[38] & ~0x18) | 0x10
    return bytes(result)


def nvm_is_9v(image):
    return len(image) == 40 and bytes(image) == nvm_9v_image(image)


def verify_runtime(bus, address=0x28, selected_mv=DEFAULT_MV):
    expected_pdos = profiles(selected_mv)
    if bus.readfrom_mem(address, 0x70, 1)[0] & 7 != 2:
        raise OSError("PD must have exactly two active profiles")
    # Ignore other capability flags when checking voltage/current/type fields.
    mask = 0xC00FFFFF
    for reg, expected in zip((0x85, 0x89, 0x8D), expected_pdos):
        actual = int.from_bytes(bus.readfrom_mem(address, reg, 4), "little")
        if (actual & mask) != (expected & mask):
            raise OSError("PD voltage/current profile changed; motor prohibited")
    if not int.from_bytes(bus.readfrom_mem(address, 0x85, 4), "little") & (1 << 26):
        raise OSError("PD USB data capability is not configured")


def configure_runtime(bus, address=0x28, selected_mv=DEFAULT_MV):
    """Low-level write; caller MUST disarm and verify motor rail discharged."""
    expected_pdos = profiles(selected_mv)
    # Restrict to mandatory PDO1 while replacing the other profiles.
    old = bus.readfrom_mem(address, 0x70, 1)[0]
    bus.writeto_mem(address, 0x70, bytes(((old & ~7) | 1,)))
    for reg, expected in zip((0x85, 0x89, 0x8D), expected_pdos):
        data = expected.to_bytes(4, "little")
        bus.writeto_mem(address, reg, data)
        if bus.readfrom_mem(address, reg, 4) != data:
            raise OSError("PD profile write did not verify")
    bus.writeto_mem(address, 0x70, bytes(((old & ~7) | 2,)))
    verify_runtime(bus, address, selected_mv)
    bus.writeto_mem(address, 0x51, b"\x0d")
    bus.writeto_mem(address, 0x1A, b"\x26")  # PD Soft_Reset, NOT an NVM reload