seveibar/nema17-stepper-motor-driver-tmc2209-usb-pd

RP2040-based USB-C PD stepper-motor controller with TMC2209 drive, AS5600 position feedback, TMP102 thermal shutdown, current/voltage telemetry, RGB status LED, and protected buzzer alarm circuitry.

Version
0.2.0
License
unset
Stars
0

firmware/power_telemetry.py

"""ADC2: PD VBUS / 11. ADC3: positive motor-supply current at 1 V/A.

ADC reference, resistor tolerances and amplifier offset affect accuracy.
This reports supply current, not individual winding currents or regeneration.
The 100 ms monitor is not a replacement for the fuse/TMC hardware protection.
"""
DIVIDER_RATIO = 11.0
SHUNT_OHMS = 0.05
CURRENT_GAIN = 20.0


def convert_adc(voltage_raw, current_raw, reference_v=3.3, current_zero_v=0.0):
    if not (0 <= voltage_raw <= 65535 and 0 <= current_raw <= 65535):
        raise ValueError("ADC value out of range")
    if not 2.7 <= reference_v <= 3.6:
        raise ValueError("Invalid calibrated ADC reference")
    voltage = voltage_raw * reference_v / 65535 * DIVIDER_RATIO
    current = max(0.0, (current_raw * reference_v / 65535 - current_zero_v)
                  / (SHUNT_OHMS * CURRENT_GAIN))
    return voltage, current


def power_acceptable(voltage, current):
    # Allow supply and measurement tolerance; reject 5/9 V fallback and 15/20 V.
    return 10.8 <= voltage <= 13.2 and 0 <= current < 1.8


class PowerTelemetry:
    def __init__(self):
        from machine import ADC, Pin
        from time import sleep_ms
        self.voltage_adc = ADC(Pin(28))
        self.current_adc = ADC(Pin(29))
        self.reference_v = 3.3  # Calibrate against the measured ADC reference.
        self.current_zero_v = 0.0
        self.voltage = self.current = None
        self.ok = False
        sleep_ms(10)  # Switched voltage divider + RC settle.

    def sample(self):
        # Discard the first conversion after switching channels.
        self.voltage_adc.read_u16()
        v = sum(self.voltage_adc.read_u16() for _ in range(16)) / 16
        self.current_adc.read_u16()
        i = sum(self.current_adc.read_u16() for _ in range(16)) / 16
        self.voltage, self.current = convert_adc(v, i, self.reference_v, self.current_zero_v)
        self.ok = power_acceptable(self.voltage, self.current)
        return self.ok