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.

Sensor de Flujo YF-S201: Medición de Caudal con Arduino
Como Hacer un Control PID de Temperatura con Arduino
Medidor de Nivel de Agua por Presión con Arduino
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.
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.
Aplicación en el Tanque Esférico
Para implementar el control PID en nuestro tanque esférico, se realiza lo siguiente:
- 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.
- Cálculo de Error: El Arduino calcula la diferencia entre el nivel deseado de agua (setpoint) y el nivel actual medido.
- 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.
- 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.
Citar este Artículo
¡Hola! Soy Sergio Andrés Castaño Giraldo. Bienvenidos a Control Automático Educación, un espacio dedicado a una de mis grandes pasiones: la Ingeniería de Control y la Automatización.
Mi objetivo aquí es tender un puente entre la teoría académica y la práctica profesional. Como profesor e investigador, he tenido la oportunidad de trabajar en proyectos complejos —desde sistemas de compresión en plataformas offshore hasta modelos de control avanzado— y todo ese aprendizaje lo vuelco en este sitio.
Aquí encontrarás material que crece constantemente: desde fundamentos de instrumentación hasta estrategias de control avanzado y programación profesional. Mi meta es que la información que comparto te sea de utilidad real en tu carrera o formación.
Te invito a que te suscribas, te unas a nuestro canal de YouTube y formemos una comunidad de aprendizaje continuo. ¡Gracias por estar aquí!