Ridimensionamento dell'uscita PID (proporzionale integrale derivata)


8

Ho implementato una funzione PID usando la formula,

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

Cosa devo fare per mantenere il mio output tra un certo intervallo? dire 0-255 Se ignoro qualsiasi valore non compreso tra 0 e 255 produce un comportamento irregolare?

Risposte:


7

È necessario gestire due problemi:

  1. overflow aritmetico
  2. avvolgimento integratore

L'overflow aritmetico è abbastanza semplice: ogni volta che esegui calcoli con numeri interi, assicurati di utilizzare valori intermedi di larghezza maggiore: ad esempio, se ae bsono a 16 bit e li aggiungi / sottrai, usa un intermedio a 32 bit e limitarlo all'intervallo di un valore a 16 bit (da 0 a 65535 per non firmato, da -32768 a 32767 per firmato) prima di eseguire il cast di nuovo a 16 bit. Se sei assolutamente sicuro di non poter mai ottenere un overflow, perché sei assolutamente sicuro degli intervalli delle variabili di input, puoi saltare questo passaggio, ma fai attenzione.

Il problema dell'integrator windup è più sottile. Se si verifica un errore di grandi dimensioni per un periodo di tempo prolungato, in modo da raggiungere il limite di saturazione dell'uscita del controller, ma l'errore è ancora diverso da zero, l'integratore continuerà ad accumulare errori, probabilmente diventando molto più grande di quanto dovrebbe raggiungere stato stazionario. Una volta che il controller esce dalla saturazione, l'integratore deve tornare indietro, causando ritardi non necessari e forse instabilità nella risposta del controller.


Su un'altra nota:

Consiglio vivamente (sì, so che questa domanda ha 18 mesi, quindi probabilmente hai finito il tuo compito, ma per il beneficio dei lettori facciamo finta di no) che calcoli il termine integrale in modo diverso: invece di Ki * (errore integrato), calcola l'integrale di (errore Ki *).

Ci sono diverse ragioni per farlo; puoi leggerli in un post sul blog che ho scritto su come implementare correttamente i controller PI .


6

Di solito limito solo il termine integrale (somma degli errori) e se non riesci a gestire lo squillo devi abbassare il guadagno per rendere il sistema troppo smorzato. Assicurati anche che le tue variabili per errore, prevError e (somma dell'errore) siano variabili più grandi che non tagliano o overflow.

Quando ritagli semplicemente la correzione e poi la riporti al termine di errore successivo, ciò causerà una non linearità e il circuito di controllo riceverà una risposta di passaggio ogni volta che ritagli che causerà il tuo comportamento nervoso.


4

Un paio di perfezionamenti che potresti prendere in considerazione:

  • generare i termini I e D corretti utilizzando filtri adeguati anziché semplicemente utilizzare somme e differenze (altrimenti si sarà molto inclini a rumore, problemi di precisione e vari altri errori). NB: assicurarsi che il termine I abbia una risoluzione sufficiente.

  • definire una banda di elica al di fuori della quale i termini D e I sono disabilitati (ovvero controllo solo proporzionale all'esterno della banda di elica, controllo PID all'interno della banda di elica)


2

Bene, come ha detto Jason S, questa domanda è vecchia :). Ma di seguito è il mio approccio. L'ho implementato su un PIC16F616 con oscillatore interno a 8 MHz, usando il compilatore XC8. Il codice dovrebbe spiegarsi nei commenti, in caso contrario, chiedimi. Inoltre, posso condividere l'intero progetto, come farò nel mio sito web in seguito.

/*
 * 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.
    }
}

usa i typedef in <stdint.h>per uint8_te uint16_t, piuttosto che unsigned inte unsigned char.
Jason S,

... ma perché diamine stai usando le unsignedvariabili per un controller PI? Questo aggiunge molta complessità al tuo codice; i if/elsecasi separati non sono necessari (a meno che non si utilizzino guadagni diversi a seconda del segno di errore) Si sta anche utilizzando il valore assoluto della derivata, che non è corretto.
Jason S,

@JasonS Al momento non ricordo, ma suppongo che in quel momento + - 127 non fosse abbastanza per me. Inoltre, non capisco come uso il valore assoluto della derivata, quale parte del codice intendi?
Abdullah Kahraman,

guarda le tue righe contenenti PID_derivativecompiti; ottieni lo stesso valore se cambi PID_errore PID_lastError. E del resto hai già perso PID_erroril segno: se l'ultima volta setMotorSpeed =8e currentMotorSpeed = 15, e questa volta setMotorSpeed = 15e currentMotorSpeed = 8, otterrai un PID_derivativevalore di 0, che è sbagliato.
Jason S,

Anche il tuo codice per i prodotti informatici è sbagliato se unsigned charè un tipo a 8 bit ed unsigned intè un tipo a 16 bit: if PID_kd = 8e PID_derivative = 32, quindi il loro prodotto sarà (unsigned char)256 == 0, perché in C, anche il prodotto di due numeri interi dello stesso tipo T è di quello stesso tipo T. Se si desidera eseguire una moltiplicazione 8x8 -> 16, è necessario eseguire il cast di uno dei termini su un numero a 16 bit senza segno prima della moltiplicazione, oppure utilizzare un compilatore intrinseco (MCHP li chiama "builtin") progettato per darti un 8x8 -> 16 moltiplicare.
Jason S,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.