Saltar al contenido
Control Automático Educación

Control PID en Tanque Esférico con Arduino

Bienvenidos controleros y controleras a una nueva entrada en nuestro sitio web, donde hoy abordaremos cómo implementar un controlador PID en Arduino para gestionar el nivel de agua en un tanque esférico.

Si te interesa profundizar en microcontroladores, sensores, programación y teoría de instrumentación y control, no olvides al mayor canal de youtube en español en estos temas.

También te puedes suscribir a este sitio web si quieres estar al tanto de las nuevas publicaciones sobre estos temas de automatización y control:

Suscríbete a este sitio WEB para estar enterado de las nuevas entradas!


Video Tutorial de Control PID con Arduino en Tanque de Nivel


Fundamentos Previos

Antes de adentrarnos en el control PID para nuestro tanque esférico, te recomiendo revisar nuestros posts anteriores. El primero sobre el uso del sensor de presión diferencial MPX10DP, que es clave para medir el nivel del líquido en el tanque. El segundo trata sobre el sensor de caudal YF-S201, que, si bien no está directamente relacionado con el PID, sienta las bases del código que desarrollaremos. Además, no olvides repasar nuestro post sobre los fundamentos y la teoría del control PID con Arduino, donde hicimos un control de temperatura, pues esa teoría será aplicada en nuestro proyecto actual.

Componentes Necesarios

Para este proyecto, necesitaremos los siguientes componentes:

  • Arduino Mega (u otro modelo de Arduino)
  • Sensor de presión MPX10DP
  • Bomba de agua de corriente continua (CC)
  • Driver de potencia BTS7960 (y se mencionará el L298)
  • Tanque con geometría esférica
  • Tubo para transportar el agua

Configuración del Hardware

En el proyecto anterior, conectamos el sensor MPX5010DP al pin A0 de Arduino y utilizamos dos bombas controladas por drivers de potencia, moduladas con la señal PWM de Arduino. Para nuestro proyecto actual, nos centraremos en la bomba conectada al driver BTS7960 en los pines 4 y 6 de Arduino.

Implementación del Control PID en Tanque con Arduino

En la implementación del control PID para gestionar el nivel de agua en un tanque esférico, nos enfrentamos a un desafío de control de procesos donde se busca la estabilidad y precisión en la respuesta del sistema. El objetivo primordial es mantener el nivel de agua dentro del tanque en un punto establecido, lo cual requiere un ajuste dinámico y continuo de la velocidad de la bomba de agua.

PID ISA

Principios del Control PID

El controlador PID (Proporcional-Integral-Derivativo) implementado en Arduino se basa en tres términos que ajustan la salida (en este caso, la velocidad de la bomba) basándose en el error, que es la diferencia entre el nivel deseado de agua (setpoint) y el nivel actual (medido por el sensor de presión diferencial):

  • Proporcional (P): Este término calcula un ajuste proporcional al error actual. Si el error es grande, el ajuste será grande, y viceversa. Esto permite una respuesta rápida a los cambios en el nivel del agua, pero solo con P, el sistema puede sobrepasar el setpoint o mantener un error constante (offset).
  • Integral (I): Acumula el error pasado y lo integra en el tiempo, permitiendo al controlador corregir desviaciones sostenidas. Ayuda a eliminar el offset producido por el término proporcional, asegurando que el nivel del agua alcance y se mantenga en el setpoint a largo plazo.
  • Derivativo (D): Este término predice los cambios futuros en el error basándose en su tasa de cambio actual. Ayuda a amortiguar la respuesta del sistema, reduciendo el sobrepaso (overshoot) y la oscilación producida por los términos P e I.
PID sintonia

Aplicación en el Tanque Esférico

Para implementar el control PID en nuestro tanque esférico, se realiza lo siguiente:

  1. Medición: Utilizamos el sensor de presión diferencial (MPX10DP) para medir continuamente el nivel de agua en el tanque. Esta señal se convierte en una lectura que el Arduino puede interpretar como el nivel actual de agua.
  2. Cálculo de Error: El Arduino calcula la diferencia entre el nivel deseado de agua (setpoint) y el nivel actual medido.
  3. Aplicación de PID: El algoritmo PID procesa el error y determina la salida adecuada para la bomba. Esto implica ajustar la señal PWM enviada al driver de potencia (BTS7960), controlando así la velocidad de la bomba para agregar o reducir agua en el tanque.
  4. Ajuste Continuo: El controlador realiza ajustes continuos en la velocidad de la bomba para mantener el nivel de agua dentro del rango deseado, respondiendo tanto a perturbaciones (como un cambio en la demanda de agua) como a variaciones constantes en el nivel.

Consideraciones de Diseño

  • Dinámica del Tanque: Dado que el tanque es esférico, la relación entre el nivel del agua y el volumen no es lineal, lo que debe considerarse en la calibración del controlador PID.
  • Parámetros PID: La sintonización de los parámetros PID (Kp, Ki, Kd) es crucial para lograr una respuesta óptima del sistema. Requiere un proceso iterativo de prueba y ajuste para encontrar la configuración ideal que minimice el error y estabilice el nivel de agua rápidamente sin causar oscilaciones excesivas.

En este caso fue empleado la técnica de Ziegler Nichols para encontrar estos parámetros:

Código Fuente

El código de Arduino para implementar el PID será detallado, explicando cómo se utiliza la lectura del sensor de presión para calcular la variable de proceso y cómo se ajusta la velocidad de la bomba según los parámetros PID.

//***************************************************//
//***************************************************//
//*****   PID Tanque Esferico                   *****//
//*****                                         *****//
//***** by: Sergio Andres Castaño Giraldo       *****//
//***** https://controlautomaticoeducacion.com/ *****//
//*****                                         *****//
//***************************************************//
//***************************************************//

#include <TimerOne.h> 

// BTS7960 - 1 (Bomba 1)
byte RPWM1 = 4;
byte LPWM1 = 6;

// l298 - 1 (Bomba 3)
byte IN3_L1 = 24;
byte IN4_L1 = 25;
byte BEN_L1 = 5;

// Sensor YF-S201 
byte flowSensor1Pin = 2;
volatile unsigned long pulseCount1 = 0;
float flowRate1 = 0.0;
unsigned long oldTime = 0;

// Sensor MPX5010DP (Presión Diferencial)
byte MPX1 = A0;

//Estructura da Bomba L298
typedef struct{
  byte enPin1;
  byte enPin2;
  byte pwmPin;
}BombaL;

//Estructura da Bomba BTS7960
typedef struct{
  byte pwmA;
  byte pwmB;
}BombaBTS;


//Crear las bombas con las estructuras
const BombaBTS bomba1 = {RPWM1, LPWM1};
const BombaL   bomba3 = {IN3_L1, IN4_L1, BEN_L1};


float inputValue  = 0;
int   velocity = 0;
float tankLevel = 0;

String inputString = "";         // Una cadena para mantener los datos recibidos
boolean stringComplete = false;  // Ya se recibió toda la cadena?

float r1=0.0;  //Referencia del Heater 1
volatile float u=0.0,u_1=0.0;    //Acción de Control
byte Ts = 1; //Periodo de muestreo
//Parámetros del PID
float kp,ti,td;
float q0,q1,q2;  
volatile float e=0.0,e_1=0.0,e_2=0.0;
      
float k=0.14,tau=35,theta=4+Ts/2;   //Parámetros del Modelo del sistema

void setup() {
  Serial.begin(9600); //Comunicación Serial
  inputString.reserve(200); // Reserva espacio para la cadena
  pinMode(flowSensor1Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(flowSensor1Pin), flowSensor1Interrupt, RISING);
  
    //Valor máximo del Timer es 8.3 Segundos
  Timer1.initialize(1000000);      //Configura el TIMER en 8 Segundos
  Timer1.attachInterrupt(SampleTime) ; //Configura la interrupción del Timer 1


  //*************************************************************************//
   //*****************   SINTONIA POR ZIEGLER y NICHOLS    *******************//
   //*************************************************************************//

   kp=(0.9*tau)/(k*theta);
   ti=3.33*theta;
   td=0.0*theta;
  

   //*************************************************************************//
   //*****************           PID DIGITAL               *******************//
   //*************************************************************************//

  // Calculo do controle PID digital
   q0=kp*(1+Ts/(2.0*ti)+td/Ts);
   q1=-kp*(1-Ts/(2.0*ti)+(2.0*td)/Ts);
   q2=(kp*td)/Ts;

}

void loop() {
  
  
  /*
  // Ativar bomba3 (BombaL) 
  digitalWrite(bomba3.enPin1, HIGH);
  digitalWrite(bomba3.enPin2, LOW);
  analogWrite(bomba3.pwmPin, velocity);
  */  

  //Leitura da altura via pressão
  tankLevel = readPressureLevel(MPX1, 5.0, 0.03);
  if(tankLevel < 0){tankLevel = 0;}
  //Serial.println("Setpoint (cm),Nivel (cm),Bomba (%)");
  Serial.print(r1);
  Serial.print(",");
  Serial.print(tankLevel);
  Serial.print(",");
  Serial.print(0);
  Serial.println();

  //Calcular flujo cada 1segundo
  if ((millis() - oldTime) > 1000) { 
    detachInterrupt(digitalPinToInterrupt(flowSensor1Pin));

    flowRate1 = (pulseCount1 * 60.0) / 7.5; // L/min (YF-S201: 1 pulso = 2.25 mL)

    // Reiniciar conteo de pulsos y tiempo
    pulseCount1 = 0;
    oldTime = millis();

    attachInterrupt(digitalPinToInterrupt(flowSensor1Pin), flowSensor1Interrupt, RISING);
  }
  delay(1000);
}

// Esta función se llama cada vez que el puerto serie recibe un byte:
void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); // Obtiene el nuevo byte
    inputString += inChar; // Añade el byte a la cadena
    if (inChar == '\n') { // Si es un byte de nueva línea, la entrada está completa
      inputValue = inputString.toInt(); // Convierte la cadena a int
      // Si inputValue está en el rango de 0 a 100
      if(inputValue > 100){inputValue = 100;}
      if(inputValue < 0){inputValue = 0;}
      inputString = ""; // Limpia la cadena
      r1 = inputValue;
    }
  }
}



//Función de la interrupción
void flowSensor1Interrupt() {
  pulseCount1++;
}

//Función del Periodo de Muestreo (Timer 1)
void SampleTime(void)
{
  PID();
}


//Lectura sensor de presion
double readPressureLevel(byte mpxPin, float Vs, float tolP) {
  // mpcPin = Pino do sensor
  // Vs = 5.0 Voltagem de alimentação (pode botar o valor exato para maior presição)
  // tolP = Tolerancia do sensor, usado para ajustar a medida
  
  double Vout, P, aux = 0;
  const int numSamples = 10;
  double rho = 997;
  double g=9.8;

  // Ler voltagem do sensor (filtro de meia móvel)
  for (int i = 0; i < numSamples; i++) {
    aux += (double(analogRead(mpxPin)) * 5.0 / 1023.0);
    delay(5);
  }
  Vout = aux / numSamples;

  // Calcular a pressão em kPa segundo equação do DataSheet (Gráfico)
  P = (Vout - 0.04 * Vs) / (0.09 * Vs) + tolP;

  // Calcular o nível do tanque em cm
  double Level = ((P * 1000) / (rho * g)) * 100;

  return Level;
}


/*===========================================================================*/
/*=======================    FUNCION DEL CONTROL PID  =======================*/
/*===========================================================================*/
void PID(void)
{
    e=(r1-tankLevel);
    // Controle PID
      u = u_1 + q0*e + q1*e_1 + q2*e_2; //Ley del controlador PID discreto
    
    if (u >= 100.0)        //Saturo la accion de control 'uT' en un tope maximo y minimo
     u = 100.0;
    
    if (u <= 0.0 || r1==0 || tankLevel >=17)
     u = 0.0;

     //u = r1;
     //Retorno a los valores reales
     e_2=e_1;
     e_1=e;
     u_1=u;
     
     //La accion calculada la transformo en PWM
     //Transforma el valor de porcentaje a velocidad
    if(r1 == 0){
      velocity = 0;
    }
    else {
      velocity = map(u, 0,100,150,255);
    }
    

    // Ativar bomba1 (BombaBTS)
    digitalWrite(bomba1.pwmA, LOW);
    analogWrite(bomba1.pwmB, velocity);
     
}

Curso Premium de Control con Arduino

Para quienes deseen profundizar en sistemas de control implementados en Arduino, invito a explorar mis cursos premium. En ellos, enseño desde la construcción económica de plantas didácticas hasta la implementación y sintonía de diversas estrategias de controladores PID, incluyendo controladores en cascada, control RST, control por variables de estado y controlador Fuzzy Mandani.

  • Curso de Sistemas de Control en Dispositivos Microcontrolados en UDEMY (PIC y ARDUINO)
  • Certificado de Aprobación una vez finalices el Curso
  • DESCUENTO si accedes directamente con los siguientes botones de acceso.
  • NOTA: Si buscas el curso directamente en UDEMY o si lo adquieres en otra plataforma distintas a las mostradas anteriormente NO OBTENDRÁS NINGUN DESCUENTO sobre el valor final del Curso.

Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:

👉 Invitar a Sergio a un Café ☕️

Que esten muy bien, nos vemos en la siguiente entrada.