Mise à l'échelle de la sortie PID (dérivée intégrale proportionnelle)

8

J'ai implémenté une fonction PID en utilisant la formule,

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

Que dois-je faire pour garder ma sortie entre une certaine plage? dire 0-255 Si j'ignore une valeur non comprise entre 0 et 255, cela produit un comportement instable?

Hamza Yerlikaya
la source

Réponses:

7

Vous devez gérer deux problèmes:

  1. débordement arithmétique
  2. liquidation de l'intégrateur

Le débordement arithmétique est assez simple - chaque fois que vous faites des mathématiques entières, assurez-vous que vous utilisez des valeurs intermédiaires de plus grande largeur: par exemple, si aet bsont 16 bits, et que vous les ajoutez / soustrayez, utilisez un intermédiaire 32 bits et limitez-la à la plage d'une valeur de 16 bits (0 à 65535 pour non signé, -32768 à 32767 pour signé) avant de redescendre à 16 bits. Si vous êtes absolument sûr de ne jamais avoir de débordement, car vous êtes absolument sûr de la plage des variables d'entrée, vous pouvez ignorer cette étape, mais attention.

Le problème de liquidation de l'intégrateur est plus subtil. Si vous avez une erreur importante pendant une période prolongée, de sorte que vous atteigniez la limite de saturation de la sortie de votre contrôleur, mais que l'erreur est toujours non nulle, l'intégrateur continuera d'accumuler l'erreur, pouvant devenir beaucoup plus importante qu'elle ne devrait le faire régime permanent. Une fois que le contrôleur sort de la saturation, l'intégrateur doit redescendre, provoquant un retard inutile et éventuellement une instabilité dans la réponse de votre contrôleur.


D'un autre côté:

Je recommanderais fortement (ouais, je sais que cette question a 18 mois, donc vous avez probablement terminé votre tâche, mais pour le bénéfice des lecteurs, faisons comme si ce n'était pas le cas) que vous calculiez le terme intégral différemment: Au lieu de Ki * (erreur intégrée), calculer l'intégrale de (erreur Ki *).

Il y a plusieurs raisons à cela; vous pouvez les lire dans un article de blog que j'ai écrit sur la façon d'implémenter correctement les contrôleurs PI .

Jason S
la source
6

Je limite généralement le terme intégral (somme des erreurs) et si vous ne pouvez pas gérer la sonnerie, vous devez baisser le gain pour rendre le système plus amorti. Assurez-vous également que vos variables d'erreur, prevError et (somme des erreurs) sont des variables plus grandes qui n'écrêtent pas ni ne dépassent.

Lorsque vous coupez simplement la correction et que vous la réintroduisez dans le terme d'erreur suivant, cela provoquera une non-linéarité et la boucle de contrôle recevra une réponse pas à pas à chaque coupure, ce qui entraînera votre comportement instable.

Rex Logan
la source
4

Quelques améliorations que vous voudrez peut-être envisager:

  • générer des termes I et D appropriés en utilisant des filtres appropriés plutôt qu'en utilisant simplement des sommes et des différences (sinon vous serez très sujet au bruit, aux problèmes de précision et à diverses autres erreurs). NB: assurez-vous que votre terme I a une résolution suffisante.

  • définir une bande d'hélice en dehors de laquelle les termes D et I sont désactivés (c.-à-d. contrôle proportionnel uniquement en dehors de la bande d'hélice, contrôle PID à l'intérieur de la bande d'hélice)

Paul R
la source
2

Eh bien, comme l'a dit Jason S, cette question est vieille :). Mais voici mon approche. J'ai implémenté cela sur un PIC16F616 fonctionnant à 8MHz oscillateur interne, en utilisant le compilateur XC8. Le code devrait s'expliquer dans les commentaires, sinon, demandez-moi. De plus, je peux partager tout le projet, comme je le ferai plus tard sur mon site Web.

/*
 * 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
la source
utilisez les typedefs <stdint.h>pour uint8_tet uint16_t, plutôt que unsigned intet unsigned char.
Jason S
... mais pourquoi diable utilisez-vous des unsignedvariables pour un contrôleur PI? Cela ajoute beaucoup de complexité à votre code; les if/elsecas séparés ne sont pas nécessaires (sauf si vous utilisez des gains différents en fonction du signe d'erreur) Vous utilisez également la valeur absolue du dérivé, ce qui est incorrect.
Jason S
@JasonS Je ne me souviens pas pour le moment, mais je suppose qu'à cette époque + - 127 n'était pas suffisant pour moi. De plus, je ne sais pas comment j'utilise la valeur absolue du dérivé, quelle partie du code voulez-vous dire?
abdullah kahraman
regardez vos lignes contenant l' PID_derivativeaffectation; vous obtenez la même valeur si vous changez PID_erroret PID_lastError. Et d'ailleurs, vous avez déjà perdu PID_errorle signe de: si la dernière fois setMotorSpeed =8et currentMotorSpeed = 15, et cette fois setMotorSpeed = 15et currentMotorSpeed = 8, alors vous obtiendrez une PID_derivativevaleur de 0, ce qui est faux.
Jason S
De plus, votre code pour les produits informatiques est incorrect s'il unsigned chars'agit d'un type 8 bits et d' unsigned intun type 16 bits: si PID_kd = 8et PID_derivative = 32, alors leur produit sera (unsigned char)256 == 0, car en C, le produit de deux entiers du même type T est également de celui même type T. Si vous voulez faire une multiplication 8x8 -> 16, vous devez transtyper l'un des termes en un nombre non signé de 16 bits avant la multiplication, ou utiliser un compilateur intrinsèque (MCHP les appelle «intégrés») conçu pour vous donne une multiplication 8x8 -> 16.
Jason S