Stepper Motor ruckelt, Adafruit TMC2209 und ESP32

Hallo,

ich habe jetzt alles soweit Aufgebaut, leider ruckelt der Stepper Motor ca alle halbe Umdrehung und läuft dann normal weiter.

Folgende Hardware benutze ich gerade.

  • ESP32 S3
  • Meanwell Netzteil 12v und 5A
  • Adafruit TMC2209 Modul

Stepper Motor:

Teilenummer des Herstellers: 17HE19-2004S

Motortyp: Bipolarer Schrittmotor

Schrittwinkel: 1,8 Grad

Haltemoment: 55 Ncm(77,88 oz.in)

Nennstrom/Phase: 2,0A

Phasenwiderstand: 1,3 Ohm

Induktivität: 2,4mH±20 %(1KHz)

Isolationsklasse: B 130°C[266°F]

Ich habe davor auch einen Stromverteiler, dort flackert dann auch die LED, wenn der Stepper ruckelt. Versucht den TMC22209 am Poti einzustellen habe ich schon, brachte auch keine Besserung.

Habt ihr noch Ideen ? Mein Netzteil müsste doch ausreichend sein, oder nicht ?

Was verstehst Du darunter?

Gruß Tommy

Ist an dem

die LED eine Anzeige für eine Strombegrenzung?

Nicht solange Du un nicht genaueres über deinen Aufbau und deinen Sketch zeigst. Bisher sind die Info's nicht ausreichend . Wir brauchen Bilder, Schaltpläne, und Programmlistings.

Eher überdimensioniert.

P.S.

Bei Meanwell Netzteilen klingeln bei mir auch immer alle Alarmglocken. Die sind zwar an sich hochwertig, aber fast alle die man hier sieht, sind Netzteile für den Schaltschrankeinbau, und gehören nicht in die Hände von Elektrotechnik-Hobbyisten.

@Tommy56: Das ist so ein Teil ( ich glaube, aus dem Modelleisenbahn Zubehör ), wo die Kabel ( 12v und GND ) vom Netzteil reinkommen und ich dann vier Ausgänge habe, jeweils mit einer LED und einer Schmelzsicherung.

@wwerner : Nur die Sicherung, ansonsten ist dort keine Begrenzung.

@MicroBahner : Den Sketch werde ich gleich hier posten. Das Netzteil ist so ein Tisch Netzteil.

PS: Das ruckeln ist jetzt beseitigt, lag am Sketch. Allerdings ist die Drehzahl relativ gering vom Stepper und ich kann diese auch nicht mehr weiter erhöhen.

# main.py
# === Test: MPX5050DP + ADS1115 + TCA9548A -> Stepper an/aus nach mbar ===
# ESP32-S3-DevKitC-1:
#   STEP -> IO1 (GPIO1)
#   EN   -> IO2 (GPIO2)  (active-low)
#
# Ablauf:
#   - Sensor lesen (Median-of-5 + EMA)
#   - p_mbar berechnen (LinReg aus u_list -> p_list_template)
#   - Hysterese:
#       p <= THRESH_ON  -> Motor START
#       p >= THRESH_OFF -> Motor STOP

from machine import Pin, I2C
from time import sleep_ms, ticks_ms, ticks_diff
from ads1115 import ADS1115
from stepper import StepperPump

# ================== Hardware Setup ==================
I2C_ID   = 0
SDA      = 8
SCL      = 9
I2C_FREQ = 100_000

TCA_ADDR = 0x70  # TCA9548A

# ================== Stepper Pins (ESP32-S3-DevKitC-1) ==================
STEPPER_STEP_PIN = 1   # IO1/GPIO1
STEPPER_EN_PIN   = 2   # IO2/GPIO2 (active-low)

# ================== ADS1115 / PGA ==================
PGA_CODE = 2           # ±2.048 V
DR       = 32          # 32 SPS

# ================== Timing / Filter ==================
SAMPLE_PERIOD_MS   = 1000
EMA_M              = 10
ALPHA              = 1.0 / EMA_M
STATUS_PERIOD_MS   = 5000
SENSOR_TIMEOUT_MS  = 8000

MEDIAN_N              = 5
MEDIAN_READ_DELAY_MS  = 35  # ~1/DR

# ================== Steuer-Grenzen ==================
THRESH_ON  = -22.0
THRESH_OFF = -20.0

# Stepper Geschwindigkeit (Steps pro Sekunde)
STEP_SPS = 5000

# ================== Sensor Setup ==================
TCA_PORT    = 0
ADS_ADDR    = 0x48
ADS_CHANNEL = 0  # A0

# ================== Kalibrierung ==================
CM_TO_MBAR = -0.980665

u_list = [
    0.173117, 0.258452, 0.345842, 0.429504, 0.515623,
    0.599765, 0.687427, 0.773606, 0.857606, 0.944102,
    1.031952, 1.116262, 1.200392
]
cm_list = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
p_list_template = [CM_TO_MBAR * cm for cm in cm_list]

# ================== I2C / TCA ==================
i2c = I2C(I2C_ID, sda=Pin(SDA), scl=Pin(SCL), freq=I2C_FREQ)

def tca_select(port: int):
    """Aktiviert genau einen Port (0..7) auf dem TCA9548A."""
    if not (0 <= port <= 7):
        raise ValueError("TCA port must be 0..7")
    i2c.writeto(TCA_ADDR, bytes([1 << port]))


# ================== Mathe & Filter ==================
def linreg(u, p):
    n   = len(u)
    su  = sum(u)
    sp  = sum(p)
    suu = sum(ui*ui for ui in u)
    sup = sum(ui*pi for ui, pi in zip(u, p))
    denom = n*suu - su*su
    if denom == 0:
        return 0.0, 0.0, 0.0
    a = (n*sup - su*sp) / denom
    b = (sp - a*su) / n
    mean_p = sp / n
    ss_tot = sum((pi-mean_p)**2 for pi in p)
    ss_res = sum((pi-(a*ui + b))**2 for ui, pi in zip(u, p))
    r2 = 1.0 - (ss_res / ss_tot if ss_tot != 0 else 0.0)
    return a, b, r2

def median_of_n(read_fn, n=5, delay_ms=0):
    vals = []
    for _ in range(n):
        vals.append(read_fn())
        if delay_ms:
            sleep_ms(delay_ms)
    vals.sort()
    return vals[n // 2]

def ema_update(prev, x, alpha):
    return x if prev is None else (alpha * x + (1.0 - alpha) * prev)


# ================== ADS ==================
ads = ADS1115(i2c, addr=ADS_ADDR, pga=PGA_CODE, dr=DR)

def read_channel_median():
    def _read_once():
        tca_select(TCA_PORT)
        return ads.read_voltage(ADS_CHANNEL)
    return median_of_n(_read_once, n=MEDIAN_N, delay_ms=MEDIAN_READ_DELAY_MS)


# ================== Main ==================
def main():
    # Kalibrierung bestimmen
    m  = min(len(u_list), len(p_list_template))
    uu = u_list[:m]
    pp = p_list_template[:m]
    A, B, R = linreg(uu, pp)

    print("=== Stepper-Test nach mbar ===")
    print("TCA{} ADS{:02X} CH{}".format(TCA_PORT, ADS_ADDR, ADS_CHANNEL))
    print("Kalibrierung: A={:.6f} mbar/V | B={:.6f} mbar | R²={:.6f}".format(A, B, R))
    print("Grenzen: ON<={:.1f}  OFF>={:.1f}  | STEP_SPS={}".format(THRESH_ON, THRESH_OFF, STEP_SPS))
    print("Pins: STEP=IO{}  EN=IO{} (active-low)".format(STEPPER_STEP_PIN, STEPPER_EN_PIN))
    print()

    # Stepper-Objekt
    motor = StepperPump(step_pin=STEPPER_STEP_PIN, en_pin=STEPPER_EN_PIN)
    motor.stop()  # sicher aus

    last_status_ms = ticks_ms()
    last_u_ema = None
    last_p = None
    last_ts = 0
    sensor_ok = False

    try:
        while True:
            now = ticks_ms()

            # --- Sensor lesen ---
            try:
                u_raw = read_channel_median()
                u_ema = ema_update(last_u_ema, u_raw, ALPHA)
                p_mbar = A * u_ema + B

                last_u_ema = u_ema
                last_p = p_mbar
                last_ts = now
                sensor_ok = True
            except OSError:
                sensor_ok = False

            fresh = sensor_ok and (ticks_diff(now, last_ts) <= SENSOR_TIMEOUT_MS)

            # --- Motorsteuerung ---
            if not fresh:
                # Sensor weg -> Motor aus Sicherheitsgründen aus
                motor.stop()
            else:
                # Hysterese
                if (not motor.is_enabled()) and (last_p <= THRESH_ON):
                    motor.start(STEP_SPS)
                elif motor.is_enabled() and (last_p >= THRESH_OFF):
                    motor.stop()

            # --- Status ---
            if ticks_diff(now, last_status_ms) >= STATUS_PERIOD_MS:
                last_status_ms = now
                if not fresh:
                    print("[STATUS] SENSOR_FAIL | Motor={}".format("ON" if motor.is_enabled() else "OFF"))
                else:
                    print("[STATUS] U_ema={:.6f} V | p={:+7.2f} mbar | Motor={}".format(
                        last_u_ema, last_p, "ON" if motor.is_enabled() else "OFF"
                    ))

            sleep_ms(SAMPLE_PERIOD_MS)

    except KeyboardInterrupt:
        print("\n[INFO] KeyboardInterrupt -> Motor aus")
    except Exception as e:
        print("\n[ERROR]", e)
    finally:
        motor.stop()
        print("[SAFE] Motor AUS.")


main()

# stepper.py
# TMC2209/2226 STEP per PWM + Anfahr-Rampe (damit hohe SPS überhaupt anlaufen)
from machine import Pin, PWM
from time import sleep_ms

class StepperPump:
    def __init__(self, step_pin: int, en_pin: int, *, en_active_low: bool = True):
        self.step_pin = Pin(step_pin, Pin.OUT)
        self.en_pin = Pin(en_pin, Pin.OUT)
        self.en_active_low = en_active_low

        self._pwm = PWM(self.step_pin)
        self._pwm.duty_u16(0)

        self._enabled = False
        self._sps = 0

        self.stop()

    def is_enabled(self) -> bool:
        return self._enabled

    def _set_en(self, on: bool):
        # active-low: LOW = enable
        self.en_pin.value(0 if (on and self.en_active_low) else
                          1 if (not on and self.en_active_low) else
                          1 if on else 0)

    def _pwm_on(self):
        self._pwm.duty_u16(32768)  # 50%

    def _pwm_off(self):
        self._pwm.duty_u16(0)

    def set_speed(self, steps_per_second: int):
        sps = int(steps_per_second)
        if sps < 1:
            sps = 1
        self._sps = sps
        self._pwm.freq(sps)

    def start(self, target_sps: int, *, ramp_ms: int = 600, start_sps: int = 800, steps: int = 12):
        """
        Startet den Motor mit Rampe.
        - start_sps: Startfrequenz, die aus dem Stand sicher anläuft
        - ramp_ms: Gesamtdauer der Rampe
        - steps: wie viele Stufen (je mehr, desto glatter)
        """
        target = int(target_sps)
        if target < 1:
            target = 1

        # enable + PWM an
        self._set_en(True)
        self._enabled = True
        self._pwm_on()

        # Rampe
        if start_sps < 1:
            start_sps = 1
        if target <= start_sps or ramp_ms <= 0 or steps <= 1:
            self.set_speed(target)
            return

        self.set_speed(start_sps)
        sleep_ms(30)  # kurzer „Kick“, damit er sicher losläuft

        dt = max(1, ramp_ms // steps)
        for i in range(1, steps + 1):
            # linear hoch
            sps = start_sps + (target - start_sps) * i // steps
            self.set_speed(sps)
            sleep_ms(dt)

    def stop(self):
        self._pwm_off()
        self._set_en(False)
        self._enabled = False

Wie ich sehe, programmierst Du in Python. Da bin ich erstmal raus. Es ist Jahre her, dass ich mal auf einem Raspi in Python programmiert habe. Aber bedenke, dass Python eine interpretative Sprache ist, und auf so einem kleinen Processor wie dem ESP32S3 um Größenordnungen langsamer ist als ein C++ Sketch. Insofern wundert es mich nicht, dass da nur kleine Stepper-Geschwindigkeiten möglich sind.

Na, das ist aber sehr aussagekräftig :joy:. Aber wenn es tatsächlich komplett gekapselt, und keines der oftmals hier gezeigten OpenFrame Netzteile ( die dann auch einfach auf dem Tisch liegen :wink: ) ist, ok.

@MicroBahner : Stimmt ^^.

So eins ist es.