Control PID: ¿Es una buena idea agregar un retardo antes del siguiente bucle?

Jan 11 2021

Estoy implementando el control PID en c ++ para hacer que un robot de accionamiento diferencial gire un número exacto de grados, pero tengo muchos problemas.

Salida anticipada del bucle de control debido al rápido tiempo de ejecución del bucle

Si el robot mide su error en menos de .5 grados, sale del circuito de control y considera que el giro está "terminado" (el .5 es un valor aleatorio que podría cambiar en algún momento). Parece que el bucle de control está funcionando tan rápido que el robot puede girar a una velocidad muy alta, pasar el punto de ajuste y salir del bucle / cortar las potencias del motor, porque estuvo en el punto de ajuste durante un breve instante. Sé que este es todo el propósito del control PID, alcanzar con precisión el punto de ajuste sin sobrepasar, pero este problema hace que sea muy difícil ajustar las constantes PID. Por ejemplo, trato de encontrar un valor de kp tal que haya una oscilación constante, pero nunca hay ninguna oscilación porque el robot cree que ha "terminado" una vez que pasa el punto de ajuste. Para arreglar esto,Implementé un sistema en el que el robot tiene que estar en el punto de ajuste durante un cierto período de tiempo antes de salir, y esto ha sido efectivo, permitiendo que ocurra la oscilación, pero el problema de salir temprano del bucle parece un problema inusual y mi solución puede ser incorrecta.

El término D no tiene ningún efecto debido al tiempo de ejecución rápido

Una vez que tuve el robot oscilando de manera controlada usando solo P, traté de agregar D para evitar el sobreimpulso. Sin embargo, esto no tuvo ningún efecto durante la mayor parte del tiempo, porque el bucle de control se ejecuta tan rápido que 19 bucles de 20, la tasa de cambio de error es 0: el robot no se movió o no se movió lo suficiente para ello. para ser medido en ese tiempo. Imprimí el cambio por error y el término derivado de cada bucle para confirmar esto y pude ver que ambos serían 0 durante alrededor de 20 ciclos de bucle antes de tomar un valor razonable y luego volver a 0 durante otros 20 ciclos. Como dije, creo que esto se debe a que los ciclos de bucle son tan rápidos que el robot literalmente no se ha movido lo suficiente para ningún tipo de cambio notable por error.Este fue un gran problema porque significaba que el término D esencialmente no tenía ningún efecto en el movimiento del robot porque casi siempre era 0. Para solucionar este problema, intenté usar el último valor distinto de cero de la derivada en lugar de cualquier valor 0, pero esto no funcionó bien, y el robot oscilaría aleatoriamente si la última derivada no representara la tasa actual de cambio de error.

Nota: También estoy usando un pequeño avance para el coeficiente de fricción estático, y llamo a este avance "f"

¿Debo agregar un retraso?

Me di cuenta de que creo que la fuente de ambos problemas es el ciclo que se ejecuta muy, muy rápido, por lo que pensé en agregar una declaración de espera al final del ciclo. Sin embargo, parece una mala solución general ralentizar un bucle de forma intencionada. ¿Es esta una buena idea?

turnHeading(double finalAngle, double kp, double ki, double kd, double f){
    std::clock_t timer;
    timer = std::clock();

    double pastTime = 0;
    double currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);

    const double initialHeading = getHeading();
    finalAngle = angleWrapDeg(finalAngle);

    const double initialAngleDiff = initialHeading - finalAngle;
    double error = angleDiff(getHeading(), finalAngle);
    double pastError = error;

    double firstTimeAtSetpoint = 0;
    double timeAtSetPoint = 0;
    bool atSetpoint = false;

    double integral = 0;
    double derivative = 0;
    double lastNonZeroD = 0;

    while (timeAtSetPoint < .05)
    {
        updatePos(encoderL.read(), encoderR.read());
        error = angleDiff(getHeading(), finalAngle);

        currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
        double dt = currentTime - pastTime;

        double proportional = error / fabs(initialAngleDiff);
        integral += dt * ((error + pastError) / 2.0);
        double derivative = (error - pastError) / dt;
        
        //FAILED METHOD OF USING LAST NON-0 VALUE OF DERIVATIVE
        // if(epsilonEquals(derivative, 0))
        // {
        //     derivative = lastNonZeroD;
        // }
        // else
        // {
        //     lastNonZeroD = derivative;
        // }

        double power = kp * proportional + ki * integral + kd * derivative;

        if (power > 0)
        {
            setMotorPowers(-power - f, power + f);
        }
        else
        {
            setMotorPowers(-power + f, power - f);
        }

        if (fabs(error) < 2)
        {
            if (!atSetpoint)
            {
                atSetpoint = true;
                firstTimeAtSetpoint = currentTime;
            }
            else //at setpoint
            {
                timeAtSetPoint = currentTime - firstTimeAtSetpoint;
            }
        }
        else //no longer at setpoint
        {
            atSetpoint = false;
            timeAtSetPoint = 0;
        }
        pastTime = currentTime;
        pastError = error;
    }
    setMotorPowers(0, 0);
}

turnHeading(90, .37, 0, .00004, .12);

Respuestas

3 UgoPattacini Jan 12 2021 at 05:14

No desactive su controlador.

El propósito de un controlador no es solo dirigir su sistema al punto de ajuste deseado de acuerdo con una respuesta dinámica predeterminada, sino también contrarrestar los factores externos potenciales que pueden impedir esta tarea. Piense en una perturbación que alejará el sistema del punto de ajuste una vez que se haya alcanzado. Por lo tanto, el controlador siempre estará operativo (a menos que necesite cambiar la tarea en sí y, como resultado, cambiar también el controlador).

Con este fin, ciertamente necesitaría sumar la parte integral, que es responsable de lograr un error de estado estable nulo en presencia de cantidades no modeladas y perturbaciones externas.

Este es un recurso bastante relacionado: https://robotics.stackexchange.com/a/19198/6941.

Deja fuera la derivada.

El 95% de los controladores PID en las industrias son controladores PI (consulte "Sistemas de retroalimentación" Astrom, Murray) ya que la parte D puede desempeñar un papel importante solo con procesos lentos (como los relacionados con la regulación de temperatura y nivel del tanque). Definitivamente este no es tu caso. Los recursos relacionados de alguna manera sobre las dificultades de los términos derivados son:

  • https://robotics.stackexchange.com/a/21556/6941.
  • https://robotics.stackexchange.com/a/21555/6941.

Una vez que tuve el robot oscilando de manera controlada usando solo P, traté de agregar D para evitar el sobreimpulso.

Bueno, parece que está siguiendo las prescripciones de Ziegler-Nichols para ajustar su controlador. Hay tablas a las que debe atenerse que le brindan estimaciones de las ganancias. Sin embargo, estos son heurísticos y es probable que no funcionen en su caso.

Simplemente suelte la parte D y concéntrese en el controlador PI. También hay otra variante de ZN que no depende de oscilaciones generadas a propósito:https://robotics.stackexchange.com/a/21616/6941.

Nunca agregue retrasos al sistema.

Los retrasos son malos con los que lidiar en un circuito de control, y las peores bestias contra las que los ingenieros tienen que luchar ( mira esto 🎥 ) ya que reducen significativamente el margen de fase que empuja al sistema general hacia la inestabilidad.

Si considera que su bucle es demasiado rápido, aplique la configuración de entrada al punto de ajuste (con, por ejemplo, una trayectoria de sacudida mínima ) para suavizar el inicio del paso brusco. El sistema de circuito cerrado reaccionará con más gracia. Otra posibilidad es que su ganancia P sea demasiado alta: simplemente desactive el controlador entonces.

A este respecto, si inserta el término integral I, deberá razonar sobre el tiempo de muestra $T_s$ también.

1 TimWescott Jan 12 2021 at 08:36

Sería mucho mejor determinar una frecuencia de muestreo fija para su controlador. Una regla realmente aproximada es que cualquiera que sea el tiempo de asentamiento que necesite fuera del circuito una vez que esté operando en el régimen lineal , su intervalo de muestreo debe ser entre 10 y 100 veces menor que el tiempo de asentamiento. Dicho de otra manera, la frecuencia de muestreo debería ser de 10 a 100 veces más rápida que el ancho de banda de bucle deseado.

Como una pequeña ventaja adicional, esto significa que puede calcular dt fuera del bucle

Preferiblemente, la frecuencia de muestreo debería ser dictada por hardware, por ejemplo, por un temporizador de hardware, y mejor aún, el muestreo de la posición debería realizarse por hardware, activado por un temporizador. Esto alivia significativamente las limitaciones del software en tiempo real.

Si está utilizando una frecuencia de muestreo razonable y la mayoría de las veces no está registrando cambios en su codificador, entonces su codificador no tiene suficientes pasos.

No estaré de acuerdo con @Ugo_Pattachini, en el sentido de que si el circuito involucra un motor y su controlador está lo suficientemente limpio, alguna acción diferencial puede ser beneficiosa. Pero hay una buena posibilidad de que tenga que estar limitado por banda (es decir, necesita un controlador de retraso de plomo), y si está ajustando PID desde el asiento de los pantalones, es muy probable que no tenga las herramientas para establecer correctamente la limitación de banda.

Sin embargo, si no puede imponer una sincronización muy ajustada al muestrear el codificador, ni siquiera intente utilizar el control derivado. Los tiempos de muestreo irregulares más algo de velocidad en su motor generarán ruido adicional, y los controladores derivados tienden a amplificar el ruido. Si no puede disponer de una señal limpia para controlar, el control derivado no será adecuado.