Verstehen Sie den Fluss eines PI-Controllers?

Ich bin mir theoretisch bewusst, wie ein PID-Controller funktioniert, habe noch nie einen implementiert. Ich implementiere eine Steuermethode zum Ansteuern eines Ventils über PWM.

Details zum Anwendungsfall: Das System verfügt über zwei ADC-Kanäle, einen für die Eingabe und den anderen für die Rückmeldung. Das Lesen von ADC-Kanälen ist freilaufend, wobei ausreichend Proben genommen werden.

Vorhandene Implementierung: Es gibt eine Endlosschleife, die nur zwei Aufgaben erledigt: ADC-Werte lesen und PWM erzeugen. Es gibt einen Timer-Interrupt, der so konfiguriert ist, dass er bei 20 ms aufgerufen wird. Also 'Ist die Zeit abgelaufen?' im Flussdiagramm unten wird alle 20 ms mit „Ja“ bewertet. Unten ist das Flussdiagramm dessen, was ich jetzt mache.

Geben Sie hier die Bildbeschreibung ein

Folgendes ist das Programm, das ich mir anschaue:

/*
    Some information on variables that are being used:

    CURR_OUT_CH is Feedback channel
    CMD_INP_CH is the channel where external input is applied.
    So, ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] is where I am receiving the value of feedback
    And, ADC_Val.fADC_Final_mAVal[CMD_INP_CH ] is where I am receiving the value of external input that I am trying to achieve
    MAX_ALLOWABLE_DUTY_CYCLE  is a macro set to ((uint16_t)480) which Maps to 60% - This is a requirement.

        (Op-Amp output is in mV, I convert it into mA based on resistor values)

    (Config[chUser_Config_Mode].uiMaxCMD_I) is 350. (max current allowed through, in mA)
*/

#define RESTRICT(x, low, high)   (x = (x)<(low)?(low):((x)>(high)?(x=high):(x)))

typedef struct {

    float fFeedback;
    float fOutput;
    float Kp;
    float Ki;
    float fIntegralError;
    float fSetpoint;

} PIControl_t;

PIControl_t PI;
uint16_t Load_Dutycount;

void PICompute(PIControl_t *pPI) 
{
    // I know that if PI is already a global, then taking the pointer doesn't make sense here,
    // but, I may have to add another PI for a different sensor here, that is why I have used 
    // it this way!

    // Instantaneous error is always local
    float fError = 0.0;

    // The classic PID error term
    fError = pPI->fSetpoint - pPI->fFeedback;

    // Compute the integral term
    pPI->fIntegralError += (pPI->Ki * fError);

    // Run all the terms together to get the overall output
    pPI->fOutput = (pPI->Kp * fError) + (pPI->fIntegralError);
}

void Update_PWM_Module(void)
{
    // Might want to get rid of this fCount, lets see.
    float fCount = 0.0;

    // Timer hasn't generated an interrupt yet (Integration time hasn't elapsed)
    // ISR sets the bCompute variable - Flags are Not the best way, but does what it should.
    // And, Timer doesn't start counting if bCompute is set
    if(!bCompute)
    {
        // No control action needed, return!
        return;
    }

    // Assign the feedback value read for PI output computation
    PI.fFeedback = ADC_Val.fADC_Final_mAVal[CURR_OUT_CH];

    // Compute the PI Controller output
    PICompute(&PI);

    // Formulate the value to be used to generate PWM
    ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] = ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] + PI.fOutput;

    // Map Output to no. of counts
    fCount = (float) ((ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] * MAX_ALLOWABLE_DUTY_CYCLE) / (float)(Config[chUser_Config_Mode].uiMaxCMD_I));

    // Convert into compatible Duty Count type - uint16_t
    Load_Dutycount = (uint16_t) fCount;

    // Bound the output count between worst case lower and higher points
    RESTRICT(Load_Dutycount, MIN_DUTY_CYCLE_COUNT, MAX_ALLOWABLE_DUTY_CYCLE);

    // Generate PWM
    Generate_PWM(Load_Dutycount);

    // Assign the latest external input value read from ADC as the Setpoint for PI computation
    PI.fSetpoint = ADC_Val.fADC_Final_mAVal[CMD_INP_CH] ;

    // Not sure about this --- Because I think with a new Setpoint, the integrated error (which was developed based on previous Setpoints) will have no significance.
    PI.fIntegralError = 0.0;

    // Start integration all over again (Timer doesn't start counting if bCompute is set)
    bCompute = false;
}    

int main(void)
{
    // Some code for Power-ON initialization like,
    //    ADC
    //    Timer
    //    PWM
    //    PI variables
    //    Everything else which needs one-time initialization before going into the infinite loop

    while(1)
    {
        Read_ADC();
        Update_PWM_Module();
    }
}

Sobald die PWM erzeugt ist, läuft sie frei. Das Tastverhältnis bleibt konstant, es sei denn, ich ändere es, daher ändert es sich nur periodisch basierend auf der PI-Berechnung.

Zur Verdeutlichung, wenn ich sage "den Wert des integrierten Fehlers aufheben", meinte ich pPI->integralError = 0.0;das C-Programm.

Problemstellung: Die Gesamtzeit für die Ausführung der Schleife, wenn der Timer nicht abgelaufen ist, beträgt ungefähr 2 ms. Die Ausführungszeit erhöht sich natürlich, wenn die PI-Berechnung abgeschlossen ist und die PWM-Erzeugungsfunktion aufgerufen wird.

Ich prüfe die beiden Signale:
- Ausgang der Rückkopplung am Ausgang des verwendeten Operationsverstärkers.
- Eingabe in das System.

Meine Frage ist, ist der Betriebsablauf korrekt? Habe ich Recht, PWM erst nach Abschluss der PI-Berechnung zu erzeugen und den Wert des integrierten Fehlers auf 0,0 zurückzusetzen, wenn ein neuer Sollwert zugewiesen wird? Beim Testen mit einem Schritteingang von 0-4 V, 0,5 Hz auf dem Oszilloskop sehe ich, dass das System etwa 120 ms benötigt, um seinen Ausgang auf den Eingang anzuheben. Ich kann korrelieren, dass die P- und I-Werte angepasst werden müssen, um die Zeit zu verbessern. In diesem Beitrag geht es nicht viel um die Abstimmung der Werte der P- und I-Faktoren.

Verwandte Lektüre: Fragen zu electronic.stackexchange, die ich durchgelesen habe und eng damit verbunden sind:

Was meinst du mit "Wert des integrierten Fehlers annullieren"? Sie möchten wirklich Fehler zu jeder Abtastzeit integrieren. Und die Abtastzeit sollte eine feste Anzahl von Millisekunden sein. Vielleicht meinen Sie das mit "verstrichene Zeit", aber es ist nicht klar. Außerdem sollte PWM die ganze Zeit laufen (normalerweise Hardware, aber es könnten Interrupts sein) und nicht nur als Teil der PI-Berechnung generiert werden.
@SpehroPefhany: Ich habe den Beitrag bearbeitet, um die "verstrichene Zeit" und die Tatsache zu verdeutlichen, dass PWM auf jeden Fall freilaufend sein sollte.
Du hast seine erste Frage nicht beantwortet: Was meinst du mit "annullieren ...". Meine einzige andere Sorge ist, dass Sie kein Anti-Windup für den integralen Term haben. Wenn sich der Fehler aus irgendeinem Grund nie auf Null verringert, wird das Integral "aufgewickelt" und kontinuierlich ansteigen (was schließlich zu einem Überlauf führt). Wenn Sie das Problem beheben, dauert es eine Weile, bis der I-Term wieder auf einen vernünftigen Wert zurückkehrt.
@SpehroPefhany und @Transistor: Was ich mit "Wert des integrierten Fehlers annullieren" meinte, ist die pPI->integralErrorZuweisung0.0
Ich würde es vorziehen, Ihren tatsächlichen Code zu sehen, den Sie innerhalb der Schleife mit guten Namen für die Variablen verwenden. Einige Dinge könnten bei der Übersetzung verloren gehen. - Sie konvertieren Code in Flussdiagramm, ich muss das Flussdiagramm wieder in Code konvertieren.
@WedaPashi: "Integral zurücksetzen" ist vielleicht ein besserer Begriff, aber Sie sollten dies nur beim Einschalten zurücksetzen. Der Integralterm sollte das Integral aller Fehler seit dem Einschalten sein. Ich denke, der Anti-Windup sollte das Integral auf den Wert begrenzen, der den Integralterm zu 100% macht.
@HarrySvensson: Ich habe das C-Programm hinzugefügt. Danke schön.
Aber Sie haben nicht erklärt, warum Sie den Integralterm zurücksetzen / aufheben.
@Transistor: Weil ich dachte, mit einem neuen Sollwert hätte der integrierte Fehler (der auf der Grundlage früherer Sollwerte entwickelt wurde) keine Bedeutung. Ich denke, hier habe ich grundlegende Verwirrung / Unverständnis.
Was oft schwer zu verstehen ist, ist, dass der integrale Begriff als "Gedächtnis" Ihrer Ausgabe dient. Es gibt also kein "neuen ADC-Lesevorgang mit vorherigem ADC-Lesevorgang vergleichen", wie man versucht sein könnte. Stattdessen sagen Sie dem PI-Regler einfach "So sind die Dinge jetzt", und dann gibt er einen neuen Wert aus und aktualisiert den integralen Teil kontinuierlich.
Stellen Sie außerdem sicher, dass Sie überall PWM-Timer-Zyklen als interne Einheit verwenden - skalieren Sie den ADC-Lesewert frühzeitig auf Timer-Zyklen. Ein häufiger Fehler sind Programme, die ADC-Rohwerte und "abstrakte PID-Einheiten" und Zeitzyklen verwenden, anstatt überall nur dieselbe Einheit zu verwenden. Ein noch schlimmerer Fehler ist die Verwendung einer "menschlich verständlichen" Einheit wie Volt oder Ampere innerhalb der Firmware - diese Einheiten ergeben für das Programm oder die CPU keinen Sinn, sondern nur für den Programmierer. Es gibt viele Katastrophenprogramme für Anfänger, bei denen der angehende Programmierer Float einführt, nur weil er Volt als interne Einheit haben möchte.
@Lundin: In der Tat. Es hat eine Weile gedauert, bis ich das verstanden habe, als ich einige Daten hatte, mit denen ich spielen konnte, aber ich schätze Ihren Kommentar, er verdeutlicht viel über den Integrationsteil in der PI-Implementierung.
Übrigens gibt es höchstwahrscheinlich keinen Grund, warum Sie hier Float verwenden müssen. PID-Regler können mit einfacher ganzzahliger Mathematik geschrieben werden. Falls Sie also eine MCU ohne FPU verwenden, haben Sie sie einfach ohne guten Grund in die Luft gesprengt.
@Lundin: Dem muss ich zustimmen. Vor ein paar Tagen habe ich den Code verworfen, der ohne Grund riesige Gleitkommaberechnungen durchführen würde. Ich mache das einfach mit den rohen ADC-Zählungen. Das System ist viel effizienter in Bezug auf Verarbeitungszeit und Speicher als früher. Die Verwendung der PWM-Timer-Zyklen ist eine geniale Idee, auf die ich nie gekommen wäre, ich werde diese Änderung heute Abend vornehmen :-)
Irgendwo im Internet gibt es einen Open-Source-Code für einen 32-Bit-Integer-basierten PID-Controller. Vielleicht das? embeddedrelated.com/showarticle/121.php . Es ist ziemlich einfache Mathematik, ich würde meinen eigenen PID-Controller-Code teilen, aber er ist leider proprietär :(
@Lundin: Danke für den Link, und es ist völlig in Ordnung, dass ich den Code nicht von Ihnen erhalte. Offensichtlich hätte es sich für mich als sehr lehrreich erweisen können, sich Ihren Code anzusehen, der viel besser wäre als das, was ich gerade habe. Ich habe ein einfaches PI-Controller-Modul geschrieben, das gut funktioniert, als ich es auf Schritt- und Rampeneingänge getestet habe, und auf Festkommaberechnungen basiert. Ich lerne und es macht Spaß! :-)
@Lundin wie skalieren Sie ADC-Werte in PWM-Werte um? Müssen Sie es nicht für jede Last auf dem System tun?
@AbdelAleem Es sind einfache lineare Gleichungen. Sie haben ein Maximum, ein Minimum und einen Koeffizienten, dann möchten Sie, dass dies einer anderen linearen Beziehung entspricht. Normalerweise nur ADC_VAL / ADC_MAX = PWM_VAL / PWM_MAX, wo Sie PWM_VAL lösen möchten.
@Lundin ah ja, da Ihr Aktuator relativ linear ist. Aber das Problem, das ich sehe, ist, dass die lineare Gleichung von der Last abhängt, wenn ich mich nicht irre.
@Lundin korrigiere mich, wenn ich falsch liege: Diese Zuordnung von ADC_VAL zu PWM_VAL ist nur für eine bestimmte Last richtig, oder?
@AbdelAleem Was all diese Einheiten in der Praxis bedeuten, ist natürlich anwendungsspezifisch. Ihr ADC könnte viele Dinge messen und Ihre PWM-Timer-Ticks könnten auch viele Dinge bedeuten.
@Lundin Ich muss fragen: Wenn ich eine lineare Beziehung zwischen Eingangssignal und Ausgangssignal für eine bestimmte Last finde, warum brauche ich dann jemals einen Controller (wenn wir die Auswirkungen von Störungen vernachlässigen)?
@AbdelAleem Es sind nur die Skalen, die linear sind. Sie nehmen die ADC-Rohwerte und skalieren sie auf die entsprechenden PWM-Ticks. Dazwischen sitzt aber der PID-Regler, der das Verhältnis zwischen Eingang und Ausgang bestimmt. Alles, was ich in diesen Kommentaren gesagt habe, ist, überall in Ihrem Programm dieselbe Einheit zu verwenden und nicht unnötig auf eine nutzlose "vom Menschen lesbare" Einheit wie mA, Grad oder was auch immer diese Werte tatsächlich darstellen, umzuskalieren.

Antworten (1)

Ich: Aber Sie haben nicht erklärt, warum Sie den Integralterm zurücksetzen / annullieren.

Weda: Weil ich dachte, dass bei einem neuen Sollwert der integrierte Fehler (der auf der Grundlage früherer Sollwerte entwickelt wurde) keine Bedeutung hätte. Ich denke, hier habe ich grundlegende Verwirrung / Unverständnis.

Ich gebe Ihnen mein Beispiel „PI für Anfänger“, das einigen bei der Arbeit zu helfen scheint:

  • Wir verwenden einen PI-Controller für einen Auto-Tempomat.
  • Der Sollwert beträgt 80 km/h.
  • Der Proportionalbereich beträgt 10 km/h. Das bedeutet 100 % Gas bis 70 km/h und 10 % Reduzierung des Gases für jede 1 km/h über 70 km/h bis 0 % Gas bei 80 km/h.

Nur proportionale Steuerung

Geben Sie hier die Bildbeschreibung ein

Abbildung 1. Reaktion des Nur-P-Tempomaten. Beachten Sie, dass die Sollgeschwindigkeit von 80 km/h niemals erreicht wird.

Wir schalten den Tempomat ein. Er beschleunigt auf 70 km/h bei 100 % Gas. Es sollte schon klar sein, dass wir niemals 80 km/h erreichen werden, denn mit Roll- und Windwiderstand können wir 80 km/h nicht ohne Leistung halten. Nehmen wir an, er pendelt sich bei 77 km/h bei 30 % Leistung ein. Das ist das Beste, was wir mit der Nur-P-Steuerung erreichen können.

Proportional-Integral-Regelung

Geben Sie hier die Bildbeschreibung ein

Abbildung 2. Die Antwort mit der Hinzufügung einer integralen Steuerung.

Wenn die integrale Aktion hinzugefügt wird, steigt der integrale Term weiterhin mit einer Rate an, die proportional zum Fehler ist. Dies ist in der Integralkurve von Abbildung 2 als eine hohe Anfangsanstiegsrate aufgrund des großen Anfangsfehlers zu sehen, der auf einen Nullanstieg (Pegellinie) abfällt, wenn der Fehler schließlich eliminiert wird.

Geben Sie hier die Bildbeschreibung ein

Abbildung 3. Die klassische PID-Regelfunktion. Quelle: Wikipedia - PID-Regler .

Eine Sache, die mir ziemlich spät im Leben dämmerte, war, dass, wenn die integrale Aktion den Ausgang korrigiert, der Fehler auf Null fällt, sodass der Beitrag der Proportionalsteuerung ebenfalls auf Null fällt. Die Ausgabe, wenn der Fehler Null ist, wird ausschließlich durch die Integralwirkung aufrechterhalten.


Beachten Sie, dass, wenn sich der Sollwert oder die Belastung ändert (das Auto trifft auf einen Hügel oder Gegenwind), der Fehler auf einen Wert ungleich Null wechselt, der P-Regler sofort von Null ansteigt und die Integralaktion von ihrem aktuellen Wert aus fortgesetzt wird - nicht von Null.


Bei Engineers Excel gibt es einen einfachen Excel- PI-Simulator , der möglicherweise von Nutzen ist. Ich weiß nicht, ob es das Beste ist.

Dies war absolut nützlich, um die Verwirrung zu klären, und führte mich einfach dazu, den Code dafür zu schreiben, anstatt in andere Quellen zu schauen. Danke vielmals!
Wenn ich also ein Auto fahre und das Pedal drücke, um die gewünschte Geschwindigkeit zu erreichen, kann man sagen, dass dies der integrale Begriff ist, der am Werk ist? Würde keinen Sinn machen, wenn es die P-Steuerung wäre, weil ich meine Leistung nicht verringern kann, um meinen Fehler (definiert als Geschwindigkeit) zu verringern. Was nützt dann der P-Regler zur Geschwindigkeitsregelung?
@AbdelAleem (1) Nein, ich schaue auf den Tachometer und sehe, dass ich über oder unter der Geschwindigkeit bin, und nehme darauf basierend eine anfängliche Drosselklappeneinstellung vor. Das ist proportionale Steuerung. (2) Ja, Sie können Ihre Leistung verringern, indem Sie Ihren Fuß vom Pedal nehmen. (3) Fehler wird nicht als Geschwindigkeit definiert. Es ist die Differenz zwischen gewünschter Geschwindigkeit und tatsächlicher Geschwindigkeit. Es hat die gleichen Einheiten wie die Geschwindigkeit. (4) P-Regelung ist genau das. Es gibt eine Ausgabe proportional zum Fehler. Es ist sehr nützlich.
@Transistor nein, der Grund, warum Sie die gewünschte Geschwindigkeit erreichen, ist die I-Steuerung, nicht die P-Steuerung. Die P-Steuerung allein gibt Ihnen einen stationären Fehler, und Sie werden NIEMALS die gewünschte Geschwindigkeit erreichen. Sie können die Geschwindigkeit nicht verringern, wenn Sie versuchen, sie zu erreichen, was ein einzelner P-Controller tun würde. Wie können Sie die gewünschte Geschwindigkeit beibehalten, wenn der Ausgang Ihres P-Reglers an diesem Punkt Null ist? Das ist paradox.
@Transistor Natürlich wird der Fehler in Bezug auf die Geschwindigkeit definiert. Wenn es die Einheit Geschwindigkeit hat, dann wird es als Geschwindigkeit definiert.
@AbdelAleem, meine Antwort befasst sich mit dem Problem des stationären Fehlers und wie es durch integrale Maßnahmen gelöst wird.
@Transistor ja ich weiß, und es ist eine tolle Antwort =)