Hilfe bei der Einstellung eines PID Reglers

Hallo Zusammen,

ich habe erst vor kurzem mit dem Arduino angefangen zu arbeiten. Ich versuche eine Drehzahlregelung zu programmieren. Dafür habe ich einen Nema23 mit dem Treiber TB6600, den Arduino und die Soll-Drehzahl wird über einen Encoder eingestellt. Das hat alles auch soweit funktioniert und ich konnte Drehzahlen von -200 bis 200 mm/s erreichen, jedoch habe ich festgestellt, dass bei geringen Geschwindigkeiten wie bei 5 mm/s nicht tatsächlich 5 mm/s abgefahren werden, sondern die Geschwindigkeit langsamer ist. In meinem Fall braucht der gedrehte Gegenstand für eine Umdrehung 25 Sekunden in der Theorie in der Praxis braucht die eine Umdrehung ca. 5-6 Sekunden mehr. Aus diesem Grund habe ich den AMT102 V2 besorgt um die tatsächliche Geschwindigkeit zu messen und mit Hilfe eines PID Reglers diese Regeldifferenz auszugleichen. Jedoch passiert nichts bei Veränderung der Kontanten des P,I und D Anteils. Meine tatsächliche Geschwindigkeit befindet sich nach Plot immer im Bereich -2 und 2 und das bei einer Sollgeschwindigkeit von 5 mm/s. Ich weiß so langsam nicht mehr weiter, da eigentlich alles richtig angeschlossen ist.

Ich füge den Code mal hinzu und eventuell findet einer den Fehler oder kann mir seine Erfahrungen teilen. Ich wäre total dankbar und freue mich auch eine Antwort

#include <AccelStepper.h>
#include <PID_v1.h>
#include <Wire.h>

/* ──────────────────────────────────────────────
   MOTORPARAMETER
────────────────────────────────────────────── */
const int motorFullSteps = 200;     
const int microsteps     = 16;      
const float rohrDurchmesser = 41;   

const int stepsPerRev = motorFullSteps * microsteps;
const float mmPerRev  = 3.1416 * rohrDurchmesser;
const float mmPerStep = mmPerRev / stepsPerRev;

/* ── Encoder Parameter ─────────────────────── */
const int encoderPPR = 1000; // Pulses per Revolution (ANPASSEN!)
const float mmPerEncStep = mmPerRev / encoderPPR;

/* ── Pins ───────────────────────────── */
const int encA = 18;
const int encB = 19;
const int rotaryCLK = 2;
const int rotaryDT  = 4;
const int rotarySW  = 5;
const int dirPin = 6;
const int stepPin = 7;

/* ── Variablen ──────────────────────── */
volatile long motorEncoderCount = 0;
long lastEncoderCount = 0;
float measuredSpeed = 0;    

int lastRotaryCLK;
volatile int rotaryValue = 0;
float targetSpeed = 0;   
float mmPerSecPerClick = 1.0;

/* ── PID Variablen ──────────────────── */
double input, output, setpoint;
double Kp = 10, Ki = 0.5, Kd = 0.0;   
PID myPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);

/* ── Zusatz für Glättung / Deadband ───────── */
float filtSpeed = 0;
const float alpha = 0.3;               
const float deadband_mmps = 0.5;       
const float min_cmd_steps = 400;   // erstmal 0 zum Testen!

/* ── Objekte ────────────────────────── */
LiquidCrystal_I2C lcd(0x27,20,4);
AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);

/* ── Interrupt Routine ───── */
void readMotorEncoder() {
  bool a = digitalRead(encA);
  bool b = digitalRead(encB);
  if (a == b) motorEncoderCount++;
  else motorEncoderCount--;
}

/* ── SETUP ─────────────────────────── */
void setup() {
  Serial.begin(115200);

  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print("PID Drehzahlregelung");

  pinMode(rotaryCLK, INPUT_PULLUP);
  pinMode(rotaryDT,  INPUT_PULLUP);
  pinMode(rotarySW,  INPUT_PULLUP);
  lastRotaryCLK = digitalRead(rotaryCLK);

  pinMode(encA, INPUT_PULLUP);
  pinMode(encB, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(encA), readMotorEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encB), readMotorEncoder, CHANGE);

  stepper.setMaxSpeed(8000);
  stepper.setAcceleration(1500);

  setpoint = 0;                  
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(20);
  myPID.SetOutputLimits(-8000, 8000); // NEGATIVE erlaubt!
}

/* ── LOOP ─────────────────────────── */
void loop() {
  int clkState = digitalRead(rotaryCLK);
  if (clkState != lastRotaryCLK && clkState == HIGH) {
    if (digitalRead(rotaryDT) == LOW) rotaryValue--;
    else rotaryValue++;
    rotaryValue = constrain(rotaryValue, -200, 200); // NEGATIVE Werte erlaubt
    targetSpeed = rotaryValue * mmPerSecPerClick;
  }
  lastRotaryCLK = clkState;

  static unsigned long lastTime = millis();
  unsigned long now = millis();
  float dt = (now - lastTime) / 1000.0;
  if (dt >= 0.02) { 
    long delta = motorEncoderCount - lastEncoderCount;
    lastEncoderCount = motorEncoderCount;

    float rawSpeed = (delta * mmPerEncStep) / dt; 
    filtSpeed += alpha * (rawSpeed - filtSpeed);
    measuredSpeed = filtSpeed;
    lastTime = now;

    input = measuredSpeed;
    if (fabs(targetSpeed) < deadband_mmps) {
      setpoint = 0;
    } else {
      setpoint = targetSpeed;
    }

    myPID.Compute();

    double cmd = output;
    if (setpoint == 0 && fabs(input) < deadband_mmps) {
      cmd = 0;
    } else if (cmd > 0 && cmd < min_cmd_steps) {
      cmd = min_cmd_steps;
    } else if (cmd < 0 && cmd > -min_cmd_steps) {
      cmd = -min_cmd_steps;
    }

    stepper.setSpeed(cmd);
    stepper.runSpeed();

    // 🔎 Debug-Ausgabe
    double error = setpoint - input;
    Serial.print(setpoint);      Serial.print("\t"); 
    Serial.print(input);         Serial.print("\t"); 
    Serial.print(output);        Serial.print("\t");
    Serial.print(cmd);           Serial.print("\t");
    Serial.print(delta);         Serial.print("\t");
    Serial.println(rawSpeed);
  }

  static unsigned long lastLCD = 0;
  if (millis() - lastLCD > 300) {
    lcd.setCursor(0,1);
    lcd.print("Soll: "); lcd.print(setpoint,1); lcd.print(" mm/s   ");
    lcd.setCursor(0,2);
    lcd.print("Ist : "); lcd.print(measuredSpeed,1); lcd.print(" mm/s   ");
    lcd.setCursor(0,3);
    lcd.print("Out : "); lcd.print(output,0); lcd.print(" steps/s   ");
    lastLCD = millis();
  }
}

Zu was brauchst Du einen PID Regler?

Einen Regler brauchst Du wenn Du den Istwert mißt und die Ansteuerung des Motors so wählst daß dieser mit Sollwert dreht.

Ein schrittmotor dreht sich aber genau mit der Ansteuerung. Ein Impuls ist ein (Micro) Schritt. Da braucht es keine Regelung, weil der Motor keinen Schlupf hat.
Grüße Uwe

Hallo Uwe,

erstmal vielen Dank für die Antwort. Du hast auf jeden Fall recht. Genau dafür möchte ich meinen Regler verwenden, da sich in meinem Fall der Motor nicht mit der eingestellten Sollgeschwindigkeit dreht. Ich habe vorher ohne den PID Regler gearbeitet und dann entdeckt, dass die angegebenen 5 mm/s nicht korrekt sind. Vielleicht noch mehr zum Kontext: An dem Motor ist ein Rohr befestigt welches mit einer Oberflächenbehandlung versehen wird und dafür müssen Geschwindigkeiten von 0-200 mm/s eingestellt werden. Testweise habe ich bei 5 mm/s die Zeit für eine Umdrehung gemessen und diese Lag über 5 Sekunden über die theoretische Zeit. Dazu muss ich sagen, dass diese Differenz bei den höheren Geschwindigkeit so abnimmt, dass diese vernachlässigt werden kann. Mit diesem Hintergrund habe ich mit den AMT120 zugelegt um diese zeitliche Differenz auszugleichen. Unabhängig davon habe ich auch ein Programm geschrieben in dem nur der Motor läuft ohne Steuerung mit Drehzahlsteuerung über den Encoder und habe mir ausgeben lassen wie lange der Motor für eine Umdrehung braucht und dort wurde die richtige bzw. tatsächliche Zeit angezeigt, aber sobald die lcd und encoder mit im Code sind funktioniert das nicht... Anbei packe ich mal noch den Code:

#include <AccelStepper.h>
#include <LiquidCrystal_I2C.h>

/* ──────────────────────────────────────────────
   MECHANIK & TREIBER – ANPASSEN!
────────────────────────────────────────────── */
const int motorFullSteps = 200;   // Vollschritte pro Umdrehung (1,8° Motor)
const int microsteps    = 16;     // Microstepping (z.B. 1/16)
const float rohrDurchmesser = 41.0; // Rohrdurchmesser in mm

// Automatisch berechnete Werte
const int stepsPerRev = motorFullSteps * microsteps; // Schritte pro Umdrehung
const float mmPerRev  = 3.1416 * rohrDurchmesser;    // Umfang in mm
const float mmPerStep = mmPerRev / stepsPerRev;      // mm pro Schritt

// Änderung der Geschwindigkeit pro Encoder-Klick
const float mmPerSecPerClick = 0.5;  // 0,5 mm/s pro Klick

/* ── Hardware-Pins ───────────────────────────── */
const int encoderCLK = 2;
const int encoderDT  = 4;
const int encoderSW  = 5;
const int dirPin     = 6;
const int stepPin    = 7;

/* ── Globale Variablen ───────────────────────── */
int lastCLKState;
volatile int encoderValue = 0;

/* ── Objekte ─────────────────────────────────── */
LiquidCrystal_I2C lcd(0x27, 20, 4);
AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);

void setup() {
  // LCD initialisieren
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Speed (mm/s)");

  // Encoder Pins
  pinMode(encoderCLK, INPUT_PULLUP);
  pinMode(encoderDT, INPUT_PULLUP);
  pinMode(encoderSW, INPUT_PULLUP);
  lastCLKState = digitalRead(encoderCLK);

  // Stepper Setup
  stepper.setMaxSpeed(8000);     // Maximal Geschwindigkeit in Steps/s
  stepper.setAcceleration(1500); // Beschleunigung

  // Serial Debug
  Serial.begin(115200);
  Serial.println("System gestartet");
  Serial.print("mm pro Umdrehung: "); Serial.println(mmPerRev, 2);
  Serial.print("mm pro Schritt: "); Serial.println(mmPerStep, 5);
}

void loop() {
  // Encoder auslesen (kein Interrupt)
  int clkState = digitalRead(encoderCLK);
  if (clkState != lastCLKState && clkState == HIGH) {
    if (digitalRead(encoderDT) == LOW) encoderValue--;
    else encoderValue++;
    encoderValue = constrain(encoderValue, -600, 600); // Begrenzung
  }
  lastCLKState = clkState;

  // Geschwindigkeit in mm/s aus encoderValue berechnen
  float mmPerSec = encoderValue * mmPerSecPerClick;

  // Umrechnen in Schritte pro Sekunde
  float stepsPerSec = mmPerSec / mmPerStep;

  // Motor Geschwindigkeit einstellen
  stepper.setSpeed(-stepsPerSec);
  stepper.run();

  // LCD + Serial Ausgabe alle 100 ms
  static unsigned long lastUpdate = 0;
  if (millis() - lastUpdate > 100) {
    lcd.setCursor(0, 2);
    lcd.print("v="); 
    lcd.print(mmPerSec, 3);
    lcd.print(" mm/s ");

    Serial.print("Encoder steps: "); Serial.print(encoderValue);
    Serial.print(" | Speed: "); Serial.print(mmPerSec, 3);
    Serial.print(" mm/s | Steps/s: "); Serial.println(stepsPerSec, 2);

    lastUpdate = millis();
  }
}

Ich bin leider nochrecht unerfahren in diesem Bereich und freue mich über jegliches Feedback

Dann macht der Motor Schrittfehler!
Oder deine Zeitbasis ist kaputt.
Dein Decoder verliert Impulse.(oder macht welche dazu)

Klarer:
Du buddelst auf der falschen Baustelle.

Übrigens, Schrittmotore sind nicht für Geschwindigkeiten gebaut sondern zu positionieren.

Hallo,
soweit ich das verstanden habe geht es Dir doch um die Geschwindigkeit des Antriebes. Benötigst Du dazu wirklich dies Auflösung als kleinsten Winkel. Ich denke Dein Arduino kommt da einfach nicht mit wenn er noch was anderes zu tun hat. Versuch doch einfach mal damit runter zu gehen.

Lass den PID Regler weg das ist krasser Unsinn für einen Stepper. Bei einem DC Motor macht das Sinn.

Heinz

Das heisst, bei eingestellten 100mm/s dreht der auch 100mm/s, aber bei 5mm/s dreht der "nur" z.B. 4,5mm/s ?

Mich würde es nicht wundern, wenn durch die float-Rechnerei sich ein Rundungsproblem potenziert.

ergibt 0,040251655 Spätestens nach der 1 ist alles weg.

Wenn Du auf 200mm/s 10 Steps "verlierst" ist das eine andere Größenordnung, als wenn Du auf 20mm/s 10 Steps ungenauer bist.

Noch bin ich nicht ganz hintergestiegen, was die ganzen Rechnereien da machen, aber mir kommt das vor, als wäre da zuviel Overheat drin.

Hallo,

Bei niedrigen Drehzahlen passen Soll- und Istwert nicht zusammen?
Bei höheren Drehzahlen passen Soll- und Istwert zusammen?
Bei einem Schrittmotor ist eigentlich das Gegenteil der Fall.
Drehzahlen haben auch keine Einheiten wie mm/s.
Ist deine Umrechnung falsch? Von Drehzahl in Bewegung?
Schritte/Winkel pro Umdrehung in lineare Bewegung?
Welchen Motor hast du?
Netzteil? Motorversorgung?
Wie sieht der Aufbau aus?
Welche Gegenkraft wirkt am Abtrieb?
Die Problembeschreibung passt für mich nicht zusammen.