Skalierung der PID-Ausgabe (Proportional Integral Derivative)

8

Ich habe eine PID-Funktion mit der Formel implementiert,

correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)

Was soll ich tun, um meine Ausgabe zwischen einem bestimmten Bereich zu halten? sagen Sie 0-255 Wenn ich einen Wert ignoriere, der nicht zwischen 0 und 255 liegt, führt dies zu einem wackeligen Verhalten?

Hamza Yerlikaya
quelle

Antworten:

7

Sie müssen zwei Probleme behandeln:

  1. arithmetischer Überlauf
  2. Integrator Windup

Der arithmetische Überlauf recht einfach ist - wann immer Sie integer Mathe tun, stellen Sie sicher , dass Sie größere Breitenzwischenwerte verwenden: zum Beispiel, wenn aund bsind 16-Bit, und Sie addieren / subtrahieren, verwenden Sie eine 32-Bit - Zwischen und begrenzen Sie ihn auf den Bereich eines 16-Bit-Werts (0 bis 65535 für vorzeichenlose Werte, -32768 bis 32767 für vorzeichenbehaftete Werte), bevor Sie ihn auf 16 Bit zurücksetzen. Wenn Sie absolut sicher sind , dass Sie niemals überlaufen können, weil Sie absolut sicher sind , welche Bereiche die Eingabevariablen haben, können Sie diesen Schritt überspringen, aber Vorsicht.

Das Problem mit dem Integrator-Windup ist subtiler. Wenn Sie über einen längeren Zeitraum einen großen Fehler haben, so dass Sie die Sättigungsgrenze Ihres Controller-Ausgangs erreichen, der Fehler jedoch immer noch ungleich Null ist, sammelt der Integrator weiterhin Fehler an und wird möglicherweise viel größer, als er erreichen sollte Gleichgewichtszustand. Sobald der Controller nicht mehr gesättigt ist, muss der Integrator wieder heruntergefahren werden, was zu unnötiger Verzögerung und möglicherweise zu Instabilität der Controller-Reaktion führt.


Noch ein Hinweis:

Ich würde dringend empfehlen (ja, ich weiß, dass diese Frage 18 Monate alt ist, also sind Sie wahrscheinlich mit Ihrer Aufgabe fertig, aber zum Wohle der Leser tun wir so, als wäre dies nicht der Fall), den Integralbegriff anders zu berechnen: Anstelle von Ki * (integrierter Fehler), Integral von (Ki * -Fehler) berechnen.

Dafür gibt es mehrere Gründe. Sie können sie in einem Blogbeitrag lesen, den ich über die korrekte Implementierung von PI-Controllern geschrieben habe .

Jason S.
quelle
6

Normalerweise beschränke ich nur den Integralterm (Summe der Fehler) und wenn Sie nicht mit dem Klingeln umgehen können, müssen Sie die Verstärkung verringern, um das System überdämpft zu machen. Stellen Sie außerdem sicher, dass Ihre Variablen für error, prevError und (Summe der Fehler) größere Variablen sind, die nicht abgeschnitten oder überlaufen.

Wenn Sie nur die Korrektur ausschneiden und diese dann in den nächsten Fehlerterm zurückführen, führt dies zu einer Nichtlinearität, und der Regelkreis erhält bei jedem Abschneiden eine Sprungantwort, die Ihr wackeliges Verhalten verursacht.

Rex Logan
quelle
4

Einige Verbesserungen, die Sie berücksichtigen sollten:

  • Generieren Sie korrekte I- und D-Terme mit geeigneten Filtern und nicht nur mit Summen und Differenzen (andernfalls sind Sie sehr anfällig für Rauschen, Genauigkeitsprobleme und verschiedene andere Fehler). NB: Stellen Sie sicher, dass Ihr I-Begriff eine ausreichende Auflösung hat.

  • Definieren Sie ein Propellerband, außerhalb dessen D- und I-Terme deaktiviert sind (dh nur Proportionalregelung außerhalb des Propellerbandes, PID-Regelung innerhalb des Propellerbandes).

Paul R.
quelle
2

Nun, wie Jason S sagte, ist diese Frage alt :). Aber unten ist mein Ansatz. Ich habe dies auf einem PIC16F616 implementiert, der mit einem internen 8-MHz-Oszillator unter Verwendung des XC8-Compilers ausgeführt wird. Der Code sollte sich in den Kommentaren erklären, wenn nicht, frag mich. Außerdem kann ich das gesamte Projekt teilen, wie ich es später auf meiner Website tun werde.

/*
 * applyEncoder Task:
 * -----------------
 * Calculates the PID (proportional-integral-derivative) to set the motor
 * speed.
 *
 * PID_error = setMotorSpeed - currentMotorSpeed
 * PID_sum = PID_Kp * (PID_error) + PID_Ki * ∫(PID_error) + PID_Kd * (ΔPID_error)
 *
 * or if the motor is speedier than it is set;
 *
 * PID_error = currentMotorSpeed - setMotorSpeed
 * PID_sum = - PID_Kp * (PID_error) - PID_Ki * ∫(PID_error) - PID_Kd * (ΔPID_error)
 *
 * Maximum value of PID_sum will be about:
 * 127*255 + 63*Iul + 63*255 = 65500
 *
 * Where Iul is Integral upper limit and is about 250.
 * 
 * If we divide by 256, we scale that down to about 0 to 255, that is the scale
 * of the PWM value.
 *
 * This task takes about 750us. Real figure is at the debug pin.
 * 
 * This task will fire when the startPID bit is set. This happens when a
 * sample is taken, about every 50 ms. When the startPID bit is not set,
 * the task yields the control of the CPU for other tasks' use.
 */
void applyPID(void)
{
    static unsigned int PID_sum = 0; // Sum of all PID terms.
    static unsigned int PID_integral = 0; // Integral for the integral term.
    static unsigned char PID_derivative = 0; // PID derivative term.
    static unsigned char PID_error; // Error term.
    static unsigned char PID_lastError = 0; // Record of the previous error term.
    static unsigned int tmp1; // Temporary register for holding miscellaneous stuff.
    static unsigned int tmp2; // Temporary register for holding miscellaneous stuff.
    OS_initializeTask(); // Initialize the task. Needed by RTOS. See RTOS header file for the details.
    while (1)
    {
        while (!startPID) // Wait for startPID bit to be 1.
        {
            OS_yield(); // If startPID is not 1, yield the CPU to other tasks in the mean-time.
        }
        DebugPin = 1; // We will measure how much time it takes to implement a PID controller.


        if (currentMotorSpeed > setMotorSpeed) // If the motor is speedier than it is set,
        {
            // PID error is the difference between set value and current value.
            PID_error = (unsigned char) (currentMotorSpeed - setMotorSpeed);

            // Integrate errors by subtracting them from the PID_integral variable.
            if (PID_error < PID_integral) // If the subtraction will not underflow,
                PID_integral -= PID_error; // Subtract the error from the current error integration.
            else
                PID_integral = 0; // If the subtraction will underflow, then set it to zero.
            // Integral term is: Ki * ∫error
            tmp1 = PID_Ki * PID_integral;
            // Check if PID_sum will overflow in the addition of integral term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.

            if (PID_error >= PID_lastError) // If current error is bigger than last error,
                PID_derivative = (unsigned char) (PID_error - PID_lastError);
                // then calculate the derivative by subtracting them.
            else
                PID_derivative = (unsigned char) (PID_lastError - PID_error);
            // Derivative term is : Kd * d(Δerror)
            tmp1 = PID_Kd * PID_derivative;
            // Check if PID_sum will overflow in the addition of derivative term.
            if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
                PID_sum -= tmp1;
            else PID_sum = 0; // If the subtraction will underflow, then set it to zero.

            // Proportional term is: Kp * error
            tmp1 = PID_Kp * PID_error; // Calculate the proportional term.
            if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
                PID_sum -= tmp1;
            else PID_sum = 0; // If the subtraction will underflow, then set it to zero.
        }
        else // If the motor is slower than it is set,
        {
            PID_error = (unsigned char) (setMotorSpeed - currentMotorSpeed);
            // Proportional term is: Kp * error
            PID_sum = PID_Kp * PID_error;

            PID_integral += PID_error; // Add the error to the integral term.
            if (PID_integral > PID_integralUpperLimit) // If we have reached the upper limit of the integral,
                PID_integral = PID_integralUpperLimit; // then limit it there.
            // Integral term is: Ki * ∫error
            tmp1 = PID_Ki * PID_integral;
            // Check if PID_sum will overflow in the addition of integral term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.

            if (PID_error >= PID_lastError) // If current error is bigger than last error,
                PID_derivative = (unsigned char) (PID_error - PID_lastError);
                // then calculate the derivative by subtracting them.
            else
                PID_derivative = (unsigned char) (PID_lastError - PID_error);
            // Derivative term is : Kd * d(Δerror)
            tmp1 = PID_Kd * PID_derivative;
            // Check if PID_sum will overflow in the addition of derivative term.
            tmp2 = 0xFFFF - tmp1;
            if (PID_sum < tmp2)
                PID_sum += tmp1; // If it will not overflow, then add it.
            else
                PID_sum = 0xFFFF; // If it will, then saturate it.
        }

        // Scale the sum to 0 - 255 from 0 - 65535 , dividing by 256, or right shifting 8.
        PID_sum >>= 8;

        // Set the duty cycle to the calculated and scaled PID_sum.
        PWM_dutyCycle = (unsigned char) PID_sum;
        PID_lastError = PID_error; // Make the current error the last error, since it is old now.

        startPID = 0; // Clear the flag. That will let this task wait for the flag.
        DebugPin = 0; // We are finished with the PID control block.
    }
}
abdullah kahraman
quelle
Verwenden Sie die typedefs in <stdint.h>für uint8_tund uint16_tanstelle von unsigned intund unsigned char.
Jason S
... aber warum zum Teufel verwenden Sie unsignedVariablen für einen PI-Regler? Dies erhöht die Komplexität Ihres Codes. Die einzelnen if/elseFälle sind nicht erforderlich (es sei denn, Sie verwenden je nach Fehlerzeichen unterschiedliche Verstärkungen). Sie verwenden auch den absoluten Wert der Ableitung, der falsch ist.
Jason S
@JasonS Ich erinnere mich im Moment nicht, aber ich denke zu dieser Zeit war + - 127 nicht genug für mich. Außerdem verstehe ich nicht, wie ich den absoluten Wert der Ableitung verwende. Welchen Teil des Codes meinen Sie?
Abdullah Kahraman
Schauen Sie sich Ihre Zeilen mit der PID_derivativeZuordnung an. Sie erhalten den gleichen Wert, wenn Sie PID_errorund wechseln PID_lastError. Und im Übrigen haben Sie bereits das PID_errorVorzeichen verloren: Wenn Sie das letzte Mal setMotorSpeed =8und currentMotorSpeed = 15und dieses Mal setMotorSpeed = 15und currentMotorSpeed = 8, dann erhalten Sie den PID_derivativeWert 0, was falsch ist.
Jason S
Auch Ihr Code für die Berechnung von Produkten ist falsch, wenn unsigned chares sich um einen 8-Bit-Typ und unsigned inteinen 16-Bit-Typ handelt: Wenn PID_kd = 8und PID_derivative = 32, dann ist ihr Produkt (unsigned char)256 == 0, weil in C auch das Produkt zweier Ganzzahlen desselben Typs T von diesem ist Gleicher Typ T. Wenn Sie eine 8x8 -> 16-Multiplikation durchführen möchten, müssen Sie einen der Begriffe vor der Multiplikation in eine vorzeichenlose 16-Bit-Zahl umwandeln oder einen Compiler verwenden (MCHP nennt sie "Builtins") Geben Sie eine 8x8 -> 16 Multiplikation.
Jason S