PID Regelung, wo ist der Fehler?

Hallöchen,
ich möchte gerne einen Brushless DC Motor mittels einem PID-Regler auf eine gewünschte Drehzahl regeln. (PWM 1ms-2ms)
Die Drehzahl wird über eine Lichtschranke gemessen, die soweit auf +/-20 1/min genau misst.

Leider schwingt meine Drehzahl beim regelen doch recht stark um ca 500/min um die Soll-Drehzahl rum.

Angefangen habe ich mal lediglich mit dem P-Glied, und habe Kp soweit erhöht, bis ich am ehesten im Bereich meiner Soll-Drehzahl von 3.500 liege.

Anschließend wollte ich das I- und D-Glied anhängen. Leider ändert das so gut wie gar nichts, egal welche Werte ich für Ki und Kd verwende. (0,01-10) DIe Drehzahl wird nicht ruhiger und nähert sich auch nicht weiter an die 3.500 Soll an.
Ki und Kd scheinen irgendwie fast völlig ignoriert zu werden :frowning:

Ich weiß momentan einfach nicht weiter, habe ich einen Fehler im Code, hab ich was vergessen oder völlig falsche Vorstellungen von einer sauberen Drehzahlregelung und genauer geht es nicht?

Ich wäre unendlich dankbar, wenn mir jemand weiterhelfen könnte.

Liebe Grüße

#include <Servo.h>
#include <Wire.h>

Servo ESC;

//------------------
int sollDrehzahl = 3500;    
double kp = 5.3;
double ki =0.075;
double kd = 0.05;              
//------------------

const int lichtschranke = 3;
long zeitAnfang, zeitEnde, zeitDifferenz;
float istDrehzahl, error, alterError, pid_p, pid_i, pid_d, pid_pid;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  ESC.attach(6);
  ESC.write(25);
  delay(2000);
  pinMode(lichtschranke, INPUT_PULLUP);    //PinMode konfiguieren(Eingang)
}

void loop() {
  while (millis() < 15000) {                    //15 Sekunden lang folgendes machen....
    zeitAnfang = micros();                      //Startzeit nehmen
    istDrehzahl = messen();                     //Aktuelle Drehzahl messen
    
    error = sollDrehzahl - istDrehzahl;         //Fehler bzw. Abweichung von Soll-Drehzahl berechnen
    pid_p = error * kp;                         //P-Regler = Fehler*Kp
    
    pid_i = pid_i + (ki * error);               //I-Regler = pid_i + (Fehler*Ki)
    
    zeitDifferenz = zeitAnfang - zeitEnde;      //Laufzeit eines Messdurchgangs (=dt)
    pid_d = kd * ((error - alterError) / zeitDifferenz);  //D-Regler = Kd * ((Fehlerdifferenz/Laufzeit)
    
    alterError = error;                         //Aktueller Fehler wird als alter Fehler gespeichert

    pid_pid = pid_p + pid_i + pid_d;            //PID-Regler = (P+I+D) Regler

    if (pid_pid > 1800) {     //PID auf max. mögliches PWM-Signal begrenzen
      pid_pid = 1800;
    }
    if (pid_pid < 1035) {     //PID auf minimal mögliches PWM-Signal begrenzen
      pid_pid = 1035;
    }
    Serial.println(istDrehzahl);      //Aktuelle Drehzahl ausgeben
    zeitEnde = micros();              //Endzeit eines Durchgangs speichern
    ESC.writeMicroseconds(pid_pid);   //Motor mit neuem PID-Wert(=PWM) ansteuern
  }
  ESC.writeMicroseconds(1000);      //Nach 15s den Motor stoppen
}

float messen() {
  long zeit;                          
  zeit = pulseIn(lichtschranke, LOW);   //zeit = Dauer bis 
  zeit = zeit * 2;
  istDrehzahl = 1000000.0 / zeit * 60.0;
  if (zeit == 0) {
    return 0;
  } else {
    return istDrehzahl;
  }
}

(deleted)

Mir kommt es verdächtig vor, wenn bei error==0 der Proportionalanteil auch 0 wird - der sollte dann doch irgendwo im Regelbereich liegen, hier 1035-1800. Vielleicht noch einen Offset von etwa 1400 draufgeben, um ohne Abweichung wenigstens in der Mitte des Regelbereichs zu liegen?

Vermutlich fehlt überhaupt eine Normierung auf den Regelbereich, d.h. welche Drehzahlen zu den Eckwerten 1035 und 1800 gehören.

Aber wieso sollte man die Regelung nicht überhaupt dem ESC überlassen, und einfach nur das Tastverhältnis vorgeben, das der gewünschten Drehzahl entspricht?

Hallo,
zunächst solltest Du mal testen wie die Skalierung von Soll und Istwert ist. Wie hast Du denn den Sollwert skaliert. Drehzahl 0-3.500U/min denke ich mal. Wie ist der Istwert skaliert, bekommst Du da auch Werte von 0-3500 u/min. Diese beiden Werte gibst Du auf den Regler. Der Reglerausgang ist auf 1035-1800 normiert. Ich hätte das anders gemacht und den Wert des Reglers auf 0-765 (1800-1035=765) begrenzt und dann 1035 dazu addiert, Damit ist die Regelgrösse Y stetig. Die Streckenverstärkung liegt dann etwa bei 3500/765=0,22, was dann auch in Etwa dem KP des Reglers entspricht.

Wenn das soweit stimmt musst Du ganz klassisch vorgehen. I und D Anteil abschalten. Dann fängst Du mit eine kleinen KP Wert an z.B 0,1 Dabei geht es jetzt erst mal nicht darum das der Istwert dem Sollwert entspricht, es geht hier erst mal um Stabilität. Wenn der jetzt schwing ist KP schon zu groß, wenn nicht kannst Du KP vergrössern. Irgendwann fängt der Regelkreis an zu schwingen. Die reine Lehre spricht hier von einem kleinen Überschwinger von etwa 7% für den optimalen Kp Wert. Dabei wird es dann so sein das der Istwert immer noch unter dem Sollwert liegt. Es kann bei einem Sollwert von 1000U/min durchaus sein das sich ein Istwert von 500U/min einstellt.

Nun schaltest Du den I Anteil zu, dabei fängt man mit kleinen Werten für KI an, der Regler soll langsam sein Ti also groß. Wenn Du jetzt also den I Anteil eingeschaltet hast solltest Du in Etwa zunächst das gleiche Verhalten sehen wie mit dem reinen P Regler, allerdings wird sich der Istwert dann langsam dem Sollwert nähern. Nun kannst Du den KI Wert vergrössern , dabei immer wieder Sollwert ändern. Irgendwann ist dann der I-Anteil so groß das es eine stetige Kurve ergibt mit einem kleinen Überschwinger und der Istwert dem Sollwert entspricht. Wenn KI zu groß wird fängt der Regekreis an zu schwingen. Wenn Du keine Schwungmassen hast, also nur den nakten Motor , wird die Zeit TI ziemlich klein sein, im Bereich von <100ms. Der I-Anteil des optimierten Reglers liegt in der Grössenordnung des grössten Verzögerung der Regelstrecke.

Der D-Anteil dient eigendlich dazu Laststöße am Antrieb schneller ausregeln zu können. Meist macht er aber nur Probleme, insbesondere wenn man Totzeiten oder Lose hat. Macht also nur dann Sinn wenn Du Lastwechsel auf Deinem Motor hast und der Regelkreis wirklich schnell sein soll.

Noch was zum lesen link

Heinz

DrDiettrich:
Vermutlich fehlt überhaupt eine Normierung auf den Regelbereich, d.h. welche Drehzahlen zu den Eckwerten 1035 und 1800 gehören.

Aber wieso sollte man die Regelung nicht überhaupt dem ESC überlassen, und einfach nur das Tastverhältnis vorgeben, das der gewünschten Drehzahl entspricht?

Hallo,
Ähm ja, ich hab sonst außer meinem Code nichts normiert oder dergleichen :confused:
Ich dachte der ESC regelt keine Drehzahl, sondern stellt sie nur? Zumindest hab ich bei verschiedenen Lastfällen des Motor unterschiedliche Drehzahlen bei gleichem PWM-Wert.

Rentner:
Hallo,
zunächst solltest Du mal testen wie die Skalierung von Soll und Istwert ist. Wie hast Du denn den Sollwert skaliert. Drehzahl 0-3.500U/min denke ich mal. Wie ist der Istwert skaliert, bekommst Du da auch Werte von 0-3500 u/min.

Erstmal vielen lieben Dank für deine ausführliche Antwort!!!

Ich hab allerdings bisschen den Faden verloren :-[

Was genau meinst du mit Skalierung von Soll und Istwert?

Also ich habe Messungen gemacht von 1000-2000ms.
1000 bis 1033µs = 0/min
1033µs = ca 800/min, vorher dreht der Motor nicht.
1800µs = knapp 7000/min, dann zieht der Motor über 2A, was mein Netzteil nicht mehr mit macht. Daher habe ich die Grenze zwischen 1035 und 1800 gesetzt.
Ist das die Skalierung der Ist-Werte?

Es soll später das ganze Drehzahlband verfügbar sein, und über ein Potentiometer die Wunsch-Soll-Drehzahl eingestellt werden. (In 100er Schritten zwischen 1.000 und 7.000/min)

Jedenfalls habe ich den Code noch so belassen, I &D nullgesetzt und Kp von 0,1 auf 1 schrittweise erhöht.
Das sieht dann doch deutlich anders aus. (Die Kennlinien, die sich stark geähnelt haben, hab ich der Übersicht wegen rausgefiltert)

Ich würde aber trotzdem gerne das mit der Normierung/Skalierung der Werte verstehen? :slight_smile:
Ich habe den Code nun so geändert.

if (pid_pid > 765) {     //PID auf max. mögliches PWM-Signal begrenzen
      pid_pid = 765;
    }
    if (pid_pid < 0) {     //PID auf minimal mögliches PWM-Signal begrenzen
      pid_pid = 0;
    }
    Serial.println(istDrehzahl);      //Aktuelle Drehzahl ausgeben
    zeitEnde = micros();              //Endzeit eines Durchgangs speichern
    ESC.writeMicroseconds(pid_pid+1035);   //Motor mit neuem PID-Wert(=PWM) ansteuern


Das sieht nochmal deutlich besser aus. Habe ich dann das mit der Skalierung richtig gemacht?

Vielen lieben Dank und liebe Grüße

Der P Anteil sollte bei mittlerer Drehzahl (zwischen min und max) in der Mitte des Stellbereichs (1000-1800) liegen. Dann kann der Regler Abweichungen in beiden Richtungen korrigieren, ohne daß der Stellbereich verlassen wird. Z.B. sowas wie

   pid_pid = 1400 + pid_p + pid_i + pid_d;            //PID-Regler = (P+I+D) Regler

DrDiettrich:
Der P Anteil sollte bei mittlerer Drehzahl (zwischen min und max) in der Mitte des Stellbereichs (1000-1800) liegen. Dann kann der Regler Abweichungen in beiden Richtungen korrigieren, ohne daß der Stellbereich verlassen wird. Z.B. sowas wie

Das sollte etwa gegeben sein durch die Art wie der TO den Stellgrößenbereich ermittelt hat.

Ansonsten sieht das doch mal ganz gut aus. Jetzt geb ki und kd mal irgendwelche werte und zeig uns was die werte pid_i und pid_d machen!

Falsch ist eigentlich, dass du beim Integral die Zeit nicht einbeziehst aber funktionieren sollte das dennoch halbwegs. Ich hab gute Erfahrungen mit getimten schleifen für Regler gemacht, also z.B. alle 5ms, das reicht für den zweck vermutlich. Kannst aber auch die differenz bilden wie du es beim D Anteil schon machst.

Rentner:
Die Streckenverstärkung liegt dann etwa bei 3500/765=0,22, was dann auch in Etwa dem KP des Reglers entspricht.

Das kam mir beim ersten Lesen schon seltsam vor, jetzt hab ich deinen Fehler gefunden. Die Streckenverstärkung ist natürlich ca. 5, der Regler sollte dann mit dem Kehrwert verstärken, den von dir genannten 0,22. So ergibt sich dann insgesamt 1. War das so gemeint?

Mahimus:
Das kam mir beim ersten Lesen schon seltsam vor, jetzt hab ich deinen Fehler gefunden. Die Streckenverstärkung ist natürlich ca. 5, der Regler sollte dann mit dem Kehrwert verstärken, den von dir genannten 0,22. So ergibt sich dann insgesamt 1. War das so gemeint?

Hallo,

ja so war das gemeint , allerdings tatsächlich nicht so geschrieben. Also schon verwirrend.

Also noch mal step by step

Ausgabe an den Motortreiber = y+offset. Bei y =0 ist die Drehzahl =0 bei 765 ist die Drehzahl 3500 dann ist die Streckenverstärkung 3500/765= 4,5 . Damit 1/4,5=0,22. als Wert für den P-Anteil.

danke für den Hinweis

Die letzte Grafik sieht doch schon mal gut aus, man sieht den Überschwinger, der ist für meinen Geschmack noch zu hoch , und man sieht die bleibende Regelabweichung. Wenn du jetzt den I Anteil vorsichtig dazu nimmst wird es zunächst links in etwa gleich aussehen und später wo die bleibende Abweichung zu sehen ist wird es langsam ansteigen bis zum Sollwert. Dann machst Du den I Anteil grösser (schneller) bis es gut ist.

Also richtig ist das Du den I-Anteil begrenzen solltest. Ich würde jeden Anteil des Reglers auf einen positiven und negativen Wert symetrisch begrenzen. Es kann ja durchaus sein das der I Anteil dem P Anteil entgegen wirken muss , damit muss er negativ sein können. Dann bildes Du die Summe aller 3 Werte und begrenzt den Wert auf 0-765. Dann addierst du den Offset 1035 dazu. Damit stellst Du sicher das keine unzulässigen Werte an den ESC gehen.

Du solltest die Optimiering des Reglers nicht für 0 und 100% machen. Mach Sollwertsprunge von 30-50%. Bei ganz kritischen Dingern macht man Sprünge von 10%.

Übrigens es gibt eine fertige Lib PID Regler kannst Du dir ja auch mal ansehen.

Gruß Heinz

Bei PID hilft eine Bibliothek wenig, die schwierige Parametrierung des Reglers wird davon nicht gelöst.