Escala de salida PID (derivada integral integral)

8

He implementado una función PID usando la fórmula,

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

¿Qué debo hacer para mantener mi salida entre un cierto rango? diga 0-255. Si ignoro cualquier valor que no esté entre 0 y 255, ¿produce un comportamiento irregular?

Hamza Yerlikaya
fuente

Respuestas:

7

Necesita manejar dos problemas:

  1. desbordamiento aritmético
  2. enrollador integrador

El desbordamiento aritmético es bastante sencillo: siempre que esté haciendo matemática entera, asegúrese de usar valores intermedios de mayor ancho: por ejemplo, si a y bson 16 bits, y los suma / resta, use un intermedio de 32 bits valor y limítelo al rango de un valor de 16 bits (0 a 65535 para sin signo, -32768 a 32767 para firmado) antes de volver a reducir a 16 bits. Si está absolutamente seguro de que nunca puede obtener un desbordamiento, porque está absolutamente seguro de los rangos de las variables de entrada, puede omitir este paso, pero tenga cuidado.

El problema del integrador windup es más sutil. Si tiene un gran error durante un período prolongado de tiempo, de modo que alcance el límite de saturación de la salida del controlador, pero el error sigue siendo distinto de cero, entonces el integrador seguirá acumulando el error, posiblemente haciéndose mucho más grande de lo que debería para lograr estado estable. Una vez que el controlador sale de la saturación, el integrador tiene que volver a bajar, causando demoras innecesarias y posiblemente inestabilidad en la respuesta de su controlador.


En otra nota:

Recomiendo encarecidamente (sí, sé que esta pregunta tiene 18 meses de edad, por lo que probablemente haya terminado su tarea, pero para beneficio de los lectores, supongamos que no lo es) que calcule el término integral de manera diferente: en lugar de Ki * (error integrado), calcule la integral de (Ki * error).

Hay varias razones para hacerlo; puede leerlos en una publicación de blog que escribí sobre cómo implementar los controladores PI correctamente .

Jason S
fuente
6

Por lo general, solo limito el término integral (suma de errores) y si no puede manejar el timbre, debe soltar la ganancia para amortiguar el sistema. También asegúrese de que sus variables de error, prevError y (suma de error) son variables más grandes que no se recortan o desbordan.

Cuando simplemente recorta la corrección y luego la retroalimenta al siguiente término de error, causará una no linealidad y el bucle de control obtendrá una respuesta escalonada cada vez que recorte, lo que causará su comportamiento irregular.

Rex Logan
fuente
4

Un par de mejoras que puede considerar:

  • generar términos I y D adecuados utilizando filtros adecuados en lugar de solo sumas y diferencias (de lo contrario, será muy propenso al ruido, problemas de precisión y otros errores). NB: asegúrese de que su término I tenga suficiente resolución.

  • definir una banda de apoyo fuera de la cual los términos D e I están deshabilitados (es decir, control proporcional solo fuera de la banda de apoyo, control PID dentro de la banda de apoyo)

Paul R
fuente
2

Bueno, como dijo Jason S, esta pregunta es vieja :). Pero a continuación está mi enfoque. Lo implementé en un PIC16F616 que funciona a un oscilador interno de 8MHz, usando el compilador XC8. El código debe explicarse en los comentarios, si no, pregúnteme. Además, puedo compartir todo el proyecto, como lo haré en mi sitio web más adelante.

/*
 * 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
fuente
use los typedefs en <stdint.h>for uint8_ty uint16_t, en lugar de unsigned inty unsigned char.
Jason S
... pero ¿por qué estás usando unsignedvariables para un controlador PI? Esto agrega mucha complejidad a su código; los if/elsecasos separados son innecesarios (a menos que use ganancias diferentes dependiendo del signo de error) También está usando el valor absoluto de la derivada, que es incorrecto.
Jason S
@JasonS No recuerdo por el momento, pero supongo que en ese momento + - 127 no fue suficiente para mí. Además, no entiendo cómo uso el valor absoluto de la derivada, ¿a qué parte del código te refieres?
Abdullah Kahraman
mira tus líneas que contienen PID_derivativeasignación; obtienes el mismo valor si cambias PID_errory PID_lastError. Y para el caso que ya ha perdido PID_errorsigno 's: si la última vez setMotorSpeed =8y currentMotorSpeed = 15, y esta vez setMotorSpeed = 15y currentMotorSpeed = 8, a continuación, obtendrá un PID_derivativevalor de 0, lo cual es incorrecto.
Jason S
Además, su código para productos informáticos es incorrecto si unsigned chares un tipo de 8 bits y unsigned intes de 16 bits: si PID_kd = 8y PID_derivative = 32, entonces su producto será (unsigned char)256 == 0, porque en C, el producto de dos enteros del mismo tipo T también es de ese tipo mismo tipo T. Si desea hacer una multiplicación 8x8 -> 16, debe convertir uno de los términos en un número de 16 bits sin signo antes de la multiplicación, o utilizar un compilador intrínseco (MCHP los llama "incorporados") diseñados para darte una multiplicación de 8x8 -> 16.
Jason S