La librería PID de Arduino nos permite implementar esta opción a nuestro código sin necesidad de programar (ni entender) la función PID, la cual hace que, por ejemplo, el control de procesos térmicos o mecánicos sea mucho más estable y se ajuste con más precisión al punto que deseamos (Setpoint).
El control PID maneja varios parámetros que describo sin exposición matemática (que no poseo):
--Input: Es la retroalimentación que da información al PID. Ejemplo, sensor de temperatura, giroscopio, tacómetro, velocímetro,etc.
--Output: Es la salida del PID para controlar un termostato, un motor, un ventilador, etc.
--Setpoint: Es el valor que deseamos mantener. Un temperatura determinada, velocidad, verticalidad de un autobalanceado, etc.
--Kp: Es el parámetro proporcional. Cuanto mayor es el número, más grande son las variaciones. Mide los valores actuales del error y si el error es grande y positivo, la corrección es grande y negativa y viceversa. Si las correcciones son muy agresivas, es fácil pasarse mucho del Setpoint deseado.
--Ki: Parámetro integral. Cuanto menor es, más rapido reacciona a los cambios pero hay más riesgo de oscilaciones. Cuenta valores de error pasados. Si la salida no es suficiente para reducir el tamaño del error, la variable de control se va acumulando con el tiempo causando que el control aplique una acción más fuerte. Corrige desviaciones pequeñas que Kp no es capaz de detectar ni corregir.
--Kd: Parámetro derivativo. A mayor valor hay más control de las oscilaciones para llegar al Setpoint. Cuenta por posibles futuros valores de error basado en la actual tasa de cambios. Disminuye progresivamente las oscilaciones ocasionadas por Kp.
Librería PID de Arduino
Con el fin de probar la bondad del control PID de la librería de Arduino, he realizado un simple experimento.
Un Arduino controla un MOSFET que alimenta a una resistencia de 18Ω a 12 voltios y encima de ella, a un centímetro, un sensor de temperatura LM35. Todo en una caja de plástico parcialmente abierta. El esquema del circuito es el mismo que el del péndulo electromagnético sustituyendo el electroimán por la dicha resistencia de 18Ω 5w o similar. A la derecha, la conexión del LM35.
En primer lugar, un intento de mantener la temperatura con el clásico sistema del termostato con funcionamiento encender-apagar dentro de unos límites. El Setpoint son 45°C y los límites 45,5 y 44,5°C.
El "contenedor" y detalle de la resistencia y LM35.
//Control On-Off de temperatura a 45°C
int LM35_PIN = 1; // Pin analógico asignado al Sensor de Temperatura LM35
int hotPin = 3; //Salida control MOSFET a calefactor
int hot=35; //valor arbitrario para la grafica
void setup() {
analogReference(INTERNAL); //Asigna referencia Arduino a 1 voltio para mejorar precision
Serial.begin(9600);
digitalWrite(hotPin, LOW);
}
void loop() {
int input = analogRead(LM35_PIN); // Obtengo el valor del LM35
float mv = (1100 / 1024.0) * input; // Pasar el valor leido a mV (el "tope" es de 1100mV)
float cel = mv / 10; // mV a grados centigrados
if (cel <= 44.5){
digitalWrite(hotPin, HIGH);
hot=40;
}
if (cel >= 45.5){
digitalWrite(hotPin, LOW);
hot=35;
}
//Salida al Serial Plotter
Serial.print(46); //linea 46 grados
Serial.print(",");
Serial.print(45); //linea Setpoint
Serial.print(",");
Serial.print(44); //linea 44 grados
Serial.print(",");
Serial.print(cel); //linea valor sensor
Serial.print(",");
Serial.println(hot); // On / OFF calefactor
delay(2000);
}
Vemos un control deficiente. Las líneas azul y verde son a 46 y 47 grados. Este control puede ser intolerable en muchos casos, por ejemplo en un cultivo celular.
Poniendo la referencia interna del Arduino a 1 voltio, la precisión del LM35 he visto que es mejor que medio grado.
Veamos como se comporta el sistema activando la librería PID para Arduino.
//Control temperatura con PID
#include <PID_v1.h>
#define PIN_LM35 1 //pin analogico
#define PIN_OUT 3 //pin de salida pwm
//Define Variables
double Setpoint, Input, Output;
//Iniciar parametros
double Kp=7, Ki=0.2, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
unsigned long previousMillis = 0;
const long interval = 2000; //Gráfica cada 2''
void setup(){
analogReference(INTERNAL); //referencia de Arduino a 1v, mejor precision LM35
Serial.begin(9600);
Setpoint = 45; //La temperatura que queremos mantener
myPID.SetMode(AUTOMATIC);
}
void loop(){
unsigned long currentMillis = millis();
int value = analogRead(PIN_LM35); //Valor del LM35
float mv = (1100 / 1024.0) * value; //Valor leido a mV (el "tope" es de 1100mV)
float cel = mv / 10; //Los mV leidos a grados celsius
Input = cel;
myPID.Compute(); //Se hace el calculo por el PID
analogWrite(PIN_OUT, Output);
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
Serial.print(46); //linea referencia +1 grado
Serial.print(",");
Serial.print(45); //linea SetPoint
Serial.print(",");
Serial.print(44); //linea referencia -1 grado
Serial.print(",");
Serial.print(Output/10); //PWM/10 para mejor gráfica
Serial.print(",");
Serial.println(Input); // Linea sensor LM35
}
}
Abajo se hace otra prueba con un sistema sin prácticamente inercia, un ventilador de ordenador de 4 hilos.
Se conecta los cable:
--Rojo a 12v
--Negro a masa
--Amarillo a pin 3 del Arduino. Es un control de las revoluciones por PWM con la circuitería interna del ventilador.
--Azul a pin2 del Arduino. Es la salida de las RPM. Es necesario conectarlo a 5V con una resistencia de 4k7.
En todas las gráficas el muestreo es cada 2''
En la gráfica izquierda se consigue un control mejor que medio grado al cabo de pocos minutos.
En la derecha, se abre la tapa de la caja al punto 350 del eje X, lo que ocasiona una perturbación térmica muy importante por corrientes de convección, a pesar de ello, el PID consigue estabilizar razonablemente
#include <PID_v1.h>
#include <MillisTimer.h> //libreria para evitar uso de delay()
const int OutPin = 3; // Salida control PWM
MillisTimer timer(500); //envia datos cada 1''
//Define Variables PID
double Setpoint, Input, Output;
//Iniciar parametros
double Kp=0.5, Ki=0.05, Kd=0.01;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
//Medir RPM ventilador PC de 4 hilos
// pin 2 cable azul rpm y unido a 5v con R de 4k7
unsigned long lastPulseTime;
unsigned long pulseInterval;
unsigned long lastUpdateTime;
uint16_t rpm;
void setup() {
Serial.begin(9600);
timer.setTimer();
lastPulseTime = 0; pulseInterval = 0;
Setpoint = 2800; //rpm que queremos mantener
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(0, 255);
myPID.SetSampleTime(10);
attachInterrupt (0, senseRotation, FALLING ); //0 = D2, FALLING
}
void loop() {
unsigned long cur = millis ();
if (cur - lastUpdateTime > 60){ // 60ms actualiza intervalo LED
rpm = 60000000 / (pulseInterval * 2); // Encuentra RPM
lastUpdateTime = cur; lastUpdateTime = cur;
}
float cel=rpm;
Input = cel;
myPID.Compute(); //Se hace el calculo por el PID
analogWrite(OutPin, Output);
if(timer.checkTimer()){
Serial.print(2900); //linea referencia +1 grado
Serial.print(",");
Serial.print(2800); //linea SetPoint
Serial.print(",");
Serial.print(2700); //linea referencia -1 grado
Serial.print(",");
Serial.print(255); //linea máxima Output
Serial.print(",");
Serial.print(Output); //PWM
Serial.print(",");
Serial.println(Input); // Linea sensor LM35
}
}
void senseRotation( void ) { // Deteccion del pulso de rotacion
unsigned long cur = micros ();
unsigned long dif = cur - lastPulseTime; //diferencia desde la bajada previa
pulseInterval = (pulseInterval - (pulseInterval >> 2)) + (dif >> 2);
lastPulseTime = cur;
}
Como el ventilador no tiene apenas inercia se alcanza el control en pocos segundos.
Por último, ¿Cómo he averiguado los valores de Kp, Ki y Kd?
Solución: Prueba y error.