Controle PID: Adicionar um atraso antes do próximo loop é uma boa ideia?
Estou implementando o controle PID em c ++ para fazer um robô de acionamento diferencial girar um número preciso de graus, mas estou tendo muitos problemas.
Saindo do loop de controle mais cedo devido ao rápido tempo de execução
Se o robô medir seu erro como sendo inferior a 0,5 graus, ele sai do loop de controle e considera a curva "concluída" (o .5 é um valor aleatório que posso alterar em algum ponto). Parece que a malha de controle está funcionando tão rapidamente que o robô pode girar em uma velocidade muito alta, passar do ponto de ajuste e sair da malha / cortar as potências do motor, porque ficou no ponto de ajuste por um breve instante. Eu sei que este é todo o propósito do controle PID, atingir com precisão o ponto de ajuste sem ultrapassar, mas este problema está tornando muito difícil ajustar as constantes PID. Por exemplo, tento encontrar um valor de kp de forma que haja oscilação constante, mas nunca haja oscilação porque o robô pensa que "terminou" depois de passar o ponto de ajuste. Para consertar isso,Implementei um sistema onde o robô tem que estar no ponto de ajuste por um certo período de tempo antes de sair, e isso tem sido eficaz, permitindo que a oscilação ocorra, mas o problema de sair do loop mais cedo parece um problema incomum e minha solução pode estar incorreto.
O termo D não tem efeito devido ao tempo de execução rápido
Uma vez que o robô oscilou de maneira controlada usando apenas P, tentei adicionar D para evitar o overshoot. No entanto, isso não surtiu efeito na maior parte do tempo, porque o loop de controle está funcionando tão rapidamente que 19 loops de 20, a taxa de alteração do erro é 0: o robô não se moveu ou não se moveu o suficiente para isso a ser medido naquele tempo. Imprimi a alteração no erro e o termo derivativo de cada loop para confirmar isso e pude ver que ambos seriam 0 por cerca de 20 ciclos de loop antes de obter um valor razoável e, em seguida, voltaria a 0 por mais 20 ciclos. Como eu disse, acho que isso ocorre porque os ciclos do loop são tão rápidos que o robô literalmente não se moveu o suficiente para qualquer tipo de alteração perceptível no erro.Isso era um grande problema porque significava que o termo D não tinha essencialmente nenhum efeito no movimento do robô porque era quase sempre 0. Para corrigir esse problema, tentei usar o último valor diferente de zero da derivada no lugar de quaisquer valores 0, mas isso não funcionou bem, e o robô iria oscilar aleatoriamente se a última derivada não representasse a taxa atual de mudança de erro.
Observação: também estou usando um pequeno feedforward para o coeficiente de atrito estático e chamo esse feedforward de "f"
Devo adicionar um atraso?
Percebi que acho que a origem de ambos os problemas é o loop rodando muito rapidamente, então algo em que pensei foi adicionar uma instrução de espera no final do loop. No entanto, parece uma solução geral ruim desacelerar intencionalmente um loop. isso é uma boa ideia?
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);
Respostas
Não desligue seu controlador.
O objetivo de um controlador não é apenas direcionar seu sistema ao ponto de ajuste desejado de acordo com uma resposta dinâmica predeterminada, mas também neutralizar fatores externos potenciais que podem impedir essa tarefa. Pense em um distúrbio que afastará o sistema do ponto de ajuste, uma vez que ele tenha sido alcançado. Assim, o controlador deve estar sempre operacional (a menos que você precise alterar a própria tarefa e, como resultado, alterar o controlador também).
Para isso, você certamente precisaria somar a parte integral, que é responsável por obter um erro de regime permanente nulo na presença de quantidades não modeladas e distúrbios externos.
Este é um recurso bastante relacionado: https://robotics.stackexchange.com/a/19198/6941.
Deixe de fora a derivada.
95% dos controladores PID nas indústrias são controladores PI (consulte "Sistemas de feedback" Astrom, Murray), pois a parte D pode desempenhar um papel significativo apenas com processos lentos (como aqueles relacionados com a temperatura e regulação do nível do tanque). Definitivamente, este não é o seu caso. Os recursos relacionados de alguma forma sobre as dificuldades dos termos derivados são:
- https://robotics.stackexchange.com/a/21556/6941.
- https://robotics.stackexchange.com/a/21555/6941.
Uma vez que o robô oscilou de maneira controlada usando apenas P, tentei adicionar D para evitar o overshoot.
Bem, parece que você está seguindo as prescrições de Ziegler-Nichols para ajustar seu controlador. Existem tabelas que você precisa seguir que fornecem estimativas de ganhos. No entanto, eles são heurísticos e é provável que não funcionem no seu caso.
Simplesmente solte a parte D e concentre-se no controlador PI. Há também outra variante ZN que não depende de oscilações geradas propositalmente:https://robotics.stackexchange.com/a/21616/6941.
Nunca, jamais, acrescente atrasos ao sistema.
Atrasos são maus para lidar com um loop de controle, e os piores monstros que os engenheiros têm que lutar ( observe isto 🎥 ), pois eles reduzem significativamente a margem de fase empurrando o sistema geral para a instabilidade.
Se você julgar que seu loop é muito rápido, aplique a formatação de entrada ao ponto de ajuste (com, por exemplo , trajetória de jerk mínimo ) para suavizar o início do passo abrupto. O sistema de malha fechada reagirá com mais elegância. Outra possibilidade é que seu ganho P esteja muito alto: apenas desafine o controlador então.
A este respeito, se você inserir o termo integral I, você precisará raciocinar sobre o tempo de amostra $T_s$ também.
Seria muito melhor determinar uma taxa de amostragem fixa para seu controlador. Uma regra básica realmente grosseira é que qualquer que seja o tempo de acomodação de que você precisa para sair do loop, uma vez que esteja operando no regime linear , seu intervalo de amostragem deve ser entre 10 vezes e 100 vezes menor que o tempo de acomodação. Em outras palavras, a taxa de amostragem deve ser de 10 a 100 vezes mais rápida do que a largura de banda do loop desejada.
Como um pequeno bônus adicional, isso significa que você pode calcular dt fora do loop
De preferência, a taxa de amostragem deve ser ditada pelo hardware, por exemplo, por um temporizador de hardware e, melhor ainda, a amostragem da posição deve ser feita por hardware, acionada por um temporizador. Isso diminui significativamente as restrições de tempo real no software.
Se você estiver usando uma taxa de amostragem razoável e, na maioria das vezes, não registrar alterações em seu codificador, então ele não tem etapas suficientes.
Vou discordar de @Ugo_Pattachini, pois se o loop envolve um motor e seu controlador é limpo o suficiente, alguma ação diferencial pode ser benéfica. Mas há uma boa chance de que ele precise ser limitado em banda (ou seja, você precisa de um controlador de lead-lag), e se você estiver fazendo um ajuste rápido de PID, há uma boa chance de você não ter as ferramentas para defina o limite de banda corretamente.
No entanto, se você não puder impor um tempo muito apertado na amostragem do codificador, nem tente usar o controle derivativo. Tempos de amostragem irregulares mais alguma velocidade em seu motor geram ruído extra, e os controladores derivados tendem a amplificar o ruído. Se você não puder organizar um sinal limpo para controlar, o controle derivativo não será adequado.