Como comandar um robô para que ele mantenha uma posição e orientação embaixo da água? Neste post veremos como podemos utilizar um controlador PID para resolver este e outros problemas de robótica.
Existem diferentes algoritmos de controle que podemos utilizar em nossos sistemas, provavelmente o mais comum algoritmo de controle fechado é o PID. Para criar uma intuição desse algoritmo, primeiro precisamos pensar o que é um controle fechado. Vamos pensar no caso de um condutor dirigindo em um terreno sinuoso. O motorista deseja manter seu veículo na velocidade da via, esse desejo do controlador, iremos chamar de “setpoint”, o motorista controla a aceleração do carro, que iremos chamar de planta utilizando o acelerador e freio, o motorista também consegue observar as mudanças que o freio e o acelerador fazem no estado do carro utilizando o velocímetro.
Este é provavelmente o exemplo mais simples e cotidiano que temos de um sistema de controle fechado, onde o condutor utiliza a observação do sistema, nesse caso o carro para atuar no mesmo, utilizando o freio e o acelerador. O diagrama para este sistema seria:
Agora que já sabemos o que é um sistema de controle fechado, ou realimentado, podemos apresentar o PID, sigla para Proporcional, Integrativo, Derivativo. Esse controlador irá utilizar o valor atual do erro (proporcional), a integral do erro (integrativo) e a derivada do erro (derivativo) para computar uma saída que iremos usar para atuar em nosso sistema, o diagrama para este sistema é o seguinte:
A equação que descreve a saída deste diagrama no tempo é a seguinte:
Iremos implementar este controlador com o objetivo de controlar a velocidade de um motor DC utilizando arduino.
Para implementarmos esse controlador, iremos utilizar a versão discreta da equação acima, substituindo a integral por um somatório e discretizando o cálculo da variação do erro.
Nosso circuito consiste de um motor que desejamos controlar, com um encoder, sensor capaz de medir a velocidade do motor e um potenciômetro que iremos utilizar para escolher a velocidade que desejamos para o motor.
Implementação do controlador:
// Mapeamento Hardware
#define PWM_OUT 3
#define ENCODER_A 2
#define POT_IN A0
int output; // define a saída de pwm
int speed; // velocidade do motor
int lastError = 0; // declara o último erro
int setpoint; // declara o setpoint
int error = 0; // erro atual
int I_error = 0; // ganho integral
int D_error = 0; // ganho derivativo
float KP = 0.1; // constante proporcional
float KI = 0.0001; // constante integrativa
float KD = 0.001; // constante derivativa
void setup() {
Serial.begin(9600); // inicia a comunicação serial
pinMode(ENCODER_A, INPUT); // define o encoder para enviar dados
pinMode(POT_IN, INPUT); // define o potenciômetro para enviar dados e controlar o valor de set point
pinMode(PWM_OUT, OUTPUT); // define a porta 3 para envio de sinal PWM para controle de velocidade
output = 10; // define um valor inicial para pwm_valor
analogWrite(PWM_OUT, output); // controla o valor pwm através da saida porta 3
}
void loop() {
// calcula o valor de set point apartir da leitura analógica do potenciometro
setpoint = map(analogRead(POT_IN), 0,1023,0,600);
// realiza a leitura do encoder e transforma o valor dos pulsos em velocidade
speed = 19.1*((60*1000*10) / pulseIn(ENCODER_A, HIGH));
error = setpoint - speed; // calcula o valor do erro
I_error += error; // somatorio do erro
D_error = lastError - error; // variação do erro
// como esse loop vai ser chamado regularmente, podemos
// supor que dt é constante e não incluilo nas contas acima.
// dt está junto de KP, KI, KD, de forma implicita.
// Implementação do Algorítimo PID
output += KP*error + KI*I_error + KD*D_error;
// limita a saida do controle para os valores vãlidos de pwm (10-255)
output = constrain(output, 10, 255);
// envia o sinal para o "motor"
analogWrite(PWM_OUT, output);
// imprime os valores de speed e setpoint.
// valores em RPM
// utilize a função de gráficos do monitor serial!
Serial.print(speed);
Serial.print(",");
Serial.println(setpoint);
}
Note que como desejamos controlar a velocidade de um motor, quando o motor estiver na velocidade igual ao setpoint, o código iria enviar um sinal nulo para o motor, levando-o a desacelerar, e assim se afastar do setpoint, para resolver este problema, somamos a saída anterior do controlador a nova saída, assim quando o erro se aproximar de 0, o controlador continuará enviando o sinal que leva o motor à velocidade desejada.
Outra modificação que realizamos na implementação acima, é a exclusão dos termos relacionados ao tempo, pois estamos chamando o controlador dentro do loop com um intervalo de tempo aproximadamente constante, assim, se dt é constante podemos realizar as seguintes simplificações:
Essas simplificações economizam
ciclos de processamento do nosso arduino, além de facilitarem a implementação do código!
Projeto do tinkercad: https://www.tinkercad.com/things/47ocvb7eAXp
Escrito por Vitor Pavani