Saída de PID (Derivada Integral Proporcional) em escala

8

Eu implementei uma função PID usando a fórmula,

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

O que devo fazer para manter minha saída entre um determinado intervalo? diga 0-255 Se eu desconsiderar qualquer valor que não esteja entre 0 e 255, ele produzirá um comportamento delicado?

Hamza Yerlikaya
fonte

Respostas:

7

Você precisa lidar com dois problemas:

  1. estouro aritmético
  2. conclusão do integrador

O estouro aritmético é bastante direto - sempre que você estiver fazendo cálculos inteiros, use valores intermediários de largura maior: por exemplo, se ae bsão de 16 bits, e você os adiciona / subtrai, usa um intermediário de 32 bits e limite-o ao intervalo de um valor de 16 bits (0 a 65535 para não assinado, -32768 a 32767 para assinado) antes de converter novamente para 16 bits. Se você tiver certeza absoluta de que nunca poderá ficar sobrecarregado, porque tem certeza absoluta dos intervalos das variáveis ​​de entrada, poderá pular esta etapa, mas tenha cuidado.

O problema de conclusão do integrador é mais sutil. Se você tiver um erro grande por um longo período de tempo, para atingir o limite de saturação da saída do controlador, mas o erro ainda for nulo, o integrador continuará acumulando erros, possivelmente ficando muito maior do que deveria alcançar curso estável. Depois que o controlador sai da saturação, o integrador precisa voltar a funcionar, causando atraso desnecessário e possivelmente instabilidade na resposta do controlador.


Em outra nota:

Eu recomendaria fortemente (sim, eu sei que essa pergunta tem 18 meses, então você provavelmente já concluiu sua tarefa, mas para o benefício dos leitores, vamos fingir que não é) que você calcule o termo integral de maneira diferente: Em vez de Ki * (erro integrado), calcule a integral de (erro Ki *).

Existem várias razões para fazê-lo; você pode lê-los em um post que escrevi sobre como implementar os controladores PI corretamente .

Jason S
fonte
6

Normalmente, apenas limite o termo integral (soma dos erros) e, se você não conseguir lidar com o toque, precisará diminuir o ganho para amortecer o sistema. Verifique também se suas variáveis ​​de erro, prevError e (soma do erro) são variáveis ​​maiores que não cortam ou transbordam.

Quando você apenas recorta a correção e depois a recoloca no próximo termo de erro, isso causa uma não linearidade e o loop de controle recebe uma resposta de etapa cada vez que você recorta, o que causará seu comportamento irregular.

Rex Logan
fonte
4

Alguns refinamentos que você pode querer considerar:

  • gere termos adequados de I e D usando filtros adequados, em vez de usar somas e diferenças (caso contrário, você estará muito propenso a ruídos, problemas de precisão e vários outros erros). Nota: verifique se o seu termo I tem resolução suficiente.

  • definir uma banda de suporte fora da qual os termos D e I estão desabilitados (isto é, controle proporcional somente fora da banda de suporte, controle PID dentro da banda de suporte)

Paul R
fonte
2

Bem, como Jason S disse, esta pergunta é antiga :). Mas abaixo está a minha abordagem. Eu implementei isso em um PIC16F616 rodando no oscilador interno de 8 MHz, usando o compilador XC8. O código deve se explicar nos comentários, se não, pergunte-me. Além disso, eu posso compartilhar todo o projeto, como farei no meu site mais tarde.

/*
 * 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
fonte
use os typedefs em <stdint.h>for uint8_te uint16_t, em vez de unsigned inte unsigned char.
Jason S
... mas por que diabos você está usando unsignedvariáveis ​​para um controlador PI? Isso adiciona muita complexidade ao seu código; os if/elsecasos separados são desnecessários (a menos que você use ganhos diferentes dependendo do sinal de erro) Você também está usando o valor absoluto da derivada, que está incorreto.
Jason S
@ Jasonon Não me lembro no momento, mas acho que naquele momento + - 127 não era suficiente para mim. Além disso, não entendo como uso o valor absoluto da derivada, qual parte do código você quer dizer?
Abdullah kahraman
olhe para suas linhas contendo PID_derivativetarefas; você obtém o mesmo valor se você alternar PID_errore PID_lastError. E, por falar nisso, você já perdeu PID_erroro sinal de s: se da última vez setMotorSpeed =8e currentMotorSpeed = 15, e dessa vez setMotorSpeed = 15e currentMotorSpeed = 8, então você obterá o PID_derivativevalor 0, que está errado.
Jason S
Além disso, seu código para produtos de computação está errado se unsigned charfor do tipo de 8 bits e do tipo de unsigned int16 bits: if PID_kd = 8e PID_derivative = 32, então, o produto será (unsigned char)256 == 0, porque em C, o produto de dois números inteiros do mesmo tipo T também é daquele mesmo tipo T. Se você deseja fazer uma multiplicação 8x8 -> 16, é necessário converter um dos termos em um número de 16 bits não assinado antes da multiplicação ou usar um compilador intrínseco (o MCHP os chama de "integrados") projetados para dê a você 8x8 -> 16 multiplicado.
Jason S