Controlador PID
O controlador PID é um dos mais utilizados na indústria para controlar sistemas de feedback. Pontos fortes são a simplicidade e a capacidade de alcançar um bom desempenho em uma ampla variedade de situações sem a necessidade de conhecer em detalhe o processo a ser controlado.
O controlador PID foi introduzido em 1911, a primeira análise teórica é de 1922. Nesses tempos, o controle PID era exclusivamente analógico. No entanto, é fácil implementar um PID digital na programação e os cálculos necessários são simples e eficientes.
Apesar da (quase)omnipresença e popularidade, o PID não é o melhor controlador disponível. No entanto, na maioria dos casos, serve muito bem. E muitos controladores “modernos” são versões melhoradas de um PID, como por exemplo PIDs com parâmetros adaptativos.
A tarefa de um controlador em malha fechada é de transformar em tempo real o sinal de erro \(e(t)=setPoint(t)-y(t)\) na entrada do controlador num sinal ou comando de controlo \(u(t)\) na saída do controlador.
O algoritmo PID (proporcional, integral, derivado) é formado pela soma de três componentes: proporcional, integral e derivado.
A função de transferência \(u(e)\) do controlador PID é definida por:
em que:
\(K_p\) ganho proporcional
\(K_i\) ganho integral
\(K_d\) ganho differential
A magnitude de cada coeficiente (ou: parâmetro) \(K_p\), \(K_i\) e \(K_d\) representa o peso respetivo no sinal de controlo. O peso pode variar entre zero e um valor máximo positivo. Controladores com um ou dois coeficientes nulos são referidos como ‘Controlador P’ ou ‘Controlador PI’ ou ‘Controlador PD’.
Uma descrição alternativa do controlador PID usa um ganha único do controlador \(K_c=K_p\) que em conjunto com constantes de tempo característicos \(T_i\) para o ganho integral e \(T_d\) para o ganho derivativo resulta na seguinte equação:
Para qualquer uma das descrições, podemos classificar o ‘papel’ de cada coeficente da seguinte maneira:
Controlo proporcional: o ganho proporcional \(K_p\) ajusta a saída do processo com base no erropresente, ou seja, quanto maior o desvio agora, maior a compensação. Esta é a parte mais intuitiva, e é sempre o primeiro parametro a ajustar. O valor do ganho proporcional deve ser ajustado de modo a chegar rápido o suficiente ao nivel desejado do sinal de saida. No entanto, é preciso ter atenção para não ‘forçar’ demasiado a saida (evitar transitórios demasiado bruscos e/ou magntitudes demasiado elevadas). Assim, ao afinar os coeficientes, é boa prática começar com valores baixos para \(K_p\), e ir incrementando ao observar a resposta do sistema.Controlo integral: o ganho integral \(K_i\) ajusta a saída com base no erro acumulado ao longo do tempo, a soma dos erros dopassado. Ajuda a eliminar o erro de estado estacionário e pode melhorar a estabilidade do sistema de controle. É uma espécie de ‘memoria’ ao longo do tempo.Controlo derivativo: o ganho differential \(K_d\) ajusta a saída com base na taxa de alteração do erro. Isto ajuda a amortecer ossciliações e melhorar a estabilidade do sistema de control. Muitas vezes é omitido porque o controlo PI é considerado suficiente. O ganho differential pode amplificar o ruído de medição e causar mudanças excessivas de saída. Filtros poderão ser importantes para obter uma melhor estimativa da taxa variável de mudança do processo. É a parte do controlador que espreita ofuturo.Para evitar mudanças abruptas na saida quando o setpoint saltar, o control derivativo pode ser calculado em função da variável de stado \(y(t)\) do sistema:\[u(t)=K_P \cdot e(t)+K_i \int_{0}^{t}e(t) - K_d \frac{d y(t)}{dt}\]
Para que a resposta PID seja ótima, é preciso ajustar corretamente os três parâmetros. Se o valor de um coeficiente for muito baixo, o efeito na saída não será notado. Se o valor de um coeficiente for muito alto, pode passar a governar o comportamento do sistema por completo, incluindo efeitos indesejáveis.
O problema na prática então é encontrar um conjunto de valores numéricos adequados para os coeficientes \(K_p\), \(K_p\) e \(K_p\) que garantem que o comportamento do sistema a controlar é de acordo com o esperado. Trata se de um problema de otimização numérica que tem na maioria dos casos o objetivo que a variável de estado do sistema (o que se pretende controlar) siga o sinal do comando \(u(t)\) da maneira mais rápida e precisa possivel, mesmo na presença de perturbações externas.
No arduino vamos implementar um controlador PID baseado em diferenças finitas:
Afinar os coeficientes PID (‘PID tuning’)
Saida do controlador PID a responder ao salto do setpoint, para três valores de Kp (Ki e Kd mantidos constantes)

Saida do controlador PID a responder ao salto do setpoint, para três valores de Ki (Kp e Kd mantidos constantes) 
Saida do controlador PID a responder ao salto do setpoint, para três valores de Kd (Kp e Ki mantidos constantes 
Impacto na variação de vários parâmetros PID (Kp, Ki, Kd) na resposta de um sistema 
Procedimento para ajuste manual
Um método de ajuste é começar com \(K_i=0\) e \(K_d=0\).
Aumentar \(K_p\) a partir de zero até que a o sinal na saida oscila; em seguida, definir para \(K_p\) para aproximadamente metade desse valor.
Em seguida, aumentar K_i até que no regime (quasi)estacionário qualquer diferença (
offset) entre valor real/medida e setpoint seja desprezável - mas parar de entrar no regime de grandes oscilações que causa instabilidade.Caso necessário, aumentar \(K_d\), até que o controlo seja suficientemente rápido para voltar a chegar ao setpoint após uma perturbação. \(K_p\) muito alto causa resposta excessiva e overshoot.
Descrição de exemplo detalhado aqui.
PID no arduino (exemplo 1)
Exemplo de implementação de controlador PID em Arduino (sem biblioteca)
//define pins
#define POT A0 //analog input for testing
//#define RPM 2
#define LED 9 //digital output for testing
//#define PWM 5
//define controller PID coefficient constants
#define KP 1.0
#define KI 0.0
#define KD 0.0
//declare controller external input/output
float setPoint, input,output; //input = feedback
//declare controller internal variable
float error,lastErr,dErr,errSum;
unsigned long now,lastTime,dt;
void setup(){
//Serial.begin(115200);
//initial values
setPoint=100.0;
errSum=0.0;
lastTime=millis();
}
float getSetpoint(){
return 100.0; //track setpoint if changes
}
float getInput(){
return 0.0; //add model...
}
void actOutput(){
analogWrite(LED,output); //exemplo
}
void updatePID(){
//get time
now = millis();
dt=now-lastTime;
//get setpoint
setPoint=getSetpoint();
//make measurement
input=getInput();
/* calculate error variables*/
error = setPoint - input;
errSum += (error * dt);
dErr = (error - lastErr) / dt;
/*update memory for next time*/
lastErr = error;
lastTime = now;
/*Compute and return PID Output*/
output= KP * error + KI * errSum + KD * dErr;
}
void showValues(){
Serial.print(setPoint);
Serial.print(',');
Serial.println(output);
}
void loop(){
updatePID();
showValues();
delay(100);
}
PID no arduino (exemplo 2)
Código de controlador PID para arduino (com biblioteca)
A implementação de um controlador PID no arduino com um código simples muitas vezes resulta bem. No entanto, para ter uma solução mais robusta como em controladores industriais, é conveniente usar uma das bibliotecas disponiveis do arduino que implementam controladores PID. Uma das (primeiras) bibliotecas foi a PID (informação detalhada cf. blog do autor). Quando instalada, ficam disponiveis na
IDE Arduino -> File -> Examples -> PIDvarios programas exemplo, como o PID_Basic.ino transcrito em baixo. Referência técnica sobre a biblioteca disponivel na página do (Arduino Playground) (click “Libraries” on the left panel. The link to the documentation is listed as “PIDLibrary - Provides basic feedback control”.).
/********************************************************
* PID Basic Example
* Reading analog input 0 to control analog PWM output 3
********************************************************/
#include <PID_v1.h>
#define PIN_INPUT 0
#define PIN_OUTPUT 3
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
double Kp=2, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
void setup()
{
//initialize the variables we're linked to
Input = analogRead(PIN_INPUT);
Setpoint = 100;
//turn the PID on
myPID.SetMode(AUTOMATIC);
}
void loop()
{
Input = analogRead(PIN_INPUT);
myPID.Compute();
analogWrite(PIN_OUTPUT, Output);
}
PID com aeropendulum simulado
Para implementar um código de exemplo para o aeropendulo foi utilizada a biblioteca PID_RTde Rob Tillaart, também disponivel via biblioteca Arduino. O código está disponivel neste simulador online (adaptado de aqui e aqui ).
//
// FILE: PID_simulation
// AUTHOR: mn
// PURPOSE: demo
//
// https://wokwi.com/projects/412312875773779969//
// adapted from https://wokwi.com/projects/356437164264235009
////
// See also https://wokwi.com/projects/357374218559137793
#include "PID_RT.h" // ALM / https://github.com/RobTillaart/PID_RT
PID_RT PID; //instantiate controller object
#define PWM 3 //PWM controller output
#define INPUT_PIN1 A0 //manual setpoint (target angle 0-thetamax deg)
#define INPUT_PIN2 A1 //manual sensor reading (measured angle 0-thetamax deg)
#define INPUT_PIN3 A2 //manual PWM controller output (0-255)
//plant (aeropendulum) start state
float trueAngle=0.0; //deg //start angle
float trueOmega=0.0; //deg/s start omega
//controller maximum ratings
float thetamax=20.0; //deg (controller input max)
float pwmmax=255; // (controller output max)
//controller setpoint
float targetAngle=trueAngle;// //deg
//float setPoint=targetAngle;
//controller input (sensor reading)
float measuredAngle=trueAngle; //deg
//float input = measuredAngle;
//controller output
float pwm;
//float output=pwm;
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.println("SetPoint(deg) Sensor(deg) PWM(0-255)");
pinMode(PWM, OUTPUT);
//set up controller
PID.setPoint(targetAngle);
PID.setOutputRange(0, pwmmax); // PWM range
PID.setInterval(100); //update time in ms
PID.setK(10, 0, 0); //PID coefficients
PID.start();
}
void loop(){
//update setPoint
targetAngle = thetamax*analogRead(A0)/1023; //pot A0
//update controller
controller();
//update plant
analogWrite(PWM, pwm); //real life
//trueAngle = simPlant(); // simulate plant and angle
trueAngle = thetamax*analogRead(A1)/1023; //regulated plant
//measurement
measuredAngle=readSensor();
//show values
show();
delay(20); //throttle
}
void controller(){
PID.setPoint(targetAngle);
if(PID.compute(measuredAngle)){
pwm = PID.getOutput();
}
}
float simPlant(){ // simulate the aeropendulum
float g = 9.81; //
float m = 0.1 ; //
float L = 0.5; //
float pwm2lift_coefficient=0.5*(m*g)/L/m/pwmmax; //% of gravitational torque
static float theta = trueAngle; //start angle (deg)
static float lasttheta = theta; //deg
static float omega = 0;
static float lastomega = omega; //deg/s
static uint32_t lastTime = 0;
uint32_t dt = 100; // ms //update period
if(millis() - lastTime >= dt){
theta = lasttheta + lastomega*dt/1000;
omega = lastomega + 180/3.14*(-g/L*sin(lasttheta*3.14/180)+ pwm2lift_coefficient*pwm)*dt/1000;
lastTime += dt; //bookkeeping references
lasttheta=theta;
lastomega=omega;
}
return theta;
}
float readSensor(){
return trueAngle; //change to real or virtual measurement
}
void show(void){
static uint32_t last = 0;
const int dt = 1000;
if (millis() - last > dt){
last += dt;
//Serial.print(millis()/1000.0);
Serial.print(PID.getSetPoint(),1);
Serial.print(' ');
Serial.print(measuredAngle,1);
Serial.print(' ');
Serial.println(byte(pwm));
}
}
/*
float driverModel(byte pwm, float Vmax){
//attention: 3.7 or 5.0 V !?
//maximum current?
return Vmax*pwm/255; //outputs dc voltage
}
float motorModel(float Vdc, float Vmax){
return Vdc*rpm/Vmax;//outputs rpm
}
float lift(){
return 0.0;
}
*/
// -- END OF FILE --
O código encontra disponivel no seguinte simulador online:
[ ]:
from IPython.display import IFrame
IFrame(width="560", height="315", src="https://wokwi.com/projects/412312875773779969")