Sistema Tinaco-Cisterna inteligente con LCD y basado en Arduino. Incluye diagrama y código fuente
En esta época donde el agua escasea, es importante tener un reservorio (cisterna) y mantener el tanque superior (tinaco) lleno el mayor tiempo posible. Me dí a la tarea de desarrollar un sistema automático inteligente que active una bomba de agua que llene el tanque cuando detecte que su nivel es menor que el de un límite inferior.
Si quieres saber qué es lo que hace a este sistema inteligente continúa leyendo para mostrártelo, o revisa el video y luego regresas por el esquemático y el código fuente:
Introducción
No estoy descubriendo el hilo negro de los sistemas automáticos Tinaco-Cisterna, lo que hice fue agregarle algunas características muy interesantes para los que nos gusta esto de la electrónica y la programación:
- Un display LCD que muestra información importante del sistema:
- El estado de los sensores.
- La cuenta actual de un temporizador regresivo programable (cuyo tiempo establecido se guarda en memoria no volátil).
- El estado en el que se encuentra el sistema en cada instante de tiempo.
- Botonera que nos permite:
- Establecer diferentes parámetros y guardarlos en memoria.
- Implementar un paro de emergencia.
- Un temporizador que salvaguarde la bomba de agua. La bomba se detendrá cuando el tiempo finalice y no se haya llenado el tinaco.
- Detección de intercambio de cables.
- Aumento del tiempo de vida de los sensores debido a la reducción de la corrosión de los mismos.
Lo mejor de todo es que desarrollé el sistema basado en el Arduino UNO. Vas a notar que utilicé una tarjeta diseñada por mí para el prototipo, pero tú podrás realizar el sistema con una tarjeta UNO común y corriente y componentes comunes en tu laboratorio.
Son tantas cosas las que desarrollé con esta aplicación que no sé por dónde empezar y de lo único que estoy seguro es que no puedo poner todo en una sola entrada del blog.
Por ello en este artículo describiré el hardware, el cual es muy simple, y luego explicaré los puntos más importantes del programa, ya que sería muy largo y difícil explicarlo todo.
Algunos elementos del software merecen su propio artículo, ya que considero que nos serán muy útiles en futuros proyectos. Por ejemplo, un artículo sobre cómo implementar diferentes bases de tiempo dentro del horrible súper-loop de Arduino.
Cómo guardar y recuperar en y desde la memoria EEPROM del chip los diferentes parámetros del sistema también merece su propio artículo. Guardar y recuperar es fácil, pero ¿cómo le haces para recuperar la información la primera vez que el sistema arranca? Se me hizo muy interesante la solución que encontré y la adaptación que le hice para este proyecto.
También merece su artículo la forma en que implementé la botonera: basado en escalera analógica, cómo lo decodifiqué, y cómo lo utilicé a lo largo del programa.
Suscríbete al blog y a mi canal de YouTube para que te lleguen las notificaciones.
Hardware
Antes de explicar los puntos más sobresalientes del proyecto, es conveniente que veamos el circuito esquemático:
Sensores
Primero lo primero, el hardware. Cualquier sistema de bombeo de agua requiere sensores que detecten los diferentes niveles del líquido en los respectivos contenedores. Sensores de nivel hay de muchos tipos:
- Eléctricos.
- Mecánicos.
- Ultrasónicos.
- Lumínicos.
- Por presión de columna de aire (barométricos).
- Por flotación.
Escogí los sensores eléctricos ya que son no invasivos; es decir, no debes perforar ni la cisterna ni el tinaco, y también son muy baratos, ya que prácticamente son los mismos cables que van del contenedor a la tarjeta electrónica.
Si tuviera que escoger sensores diferentes utilizaría los sensores por flotación, ya que también son no invasivos y son inmunes a la corrosión; sin embargo, son un poco caros.
Como tercera opción escogería los sensores por presión de columna de aire. Más complicados y caros que los dos anteriores, pero durables e interesantes. El problema con ellos es la calibración (es el mismo principio que utilizan los presostatos de tu lavadora) y la transmisión de la señal analógica desde el contenedor hasta la tarjeta electrónica.
Lo bueno del proyecto es que tú puedes decidir qué tipo de sensores quieres utilizar. Considera a los sensores eléctricos como la opción “de entrada”.
Cada sensor tiene asociada una resistencia de pull-down externa (lo que en el bajo mundo le decimos “strong pull-down”); esto significa que el sensor entregará un nivel lógico 0 cuando no haya agua, y un nivel lógico 1 cuando esté cubierto por agua.
La conexión física de los sensores en ambos tanques queda así:
Pantalla LCD
Es la clásica pantalla (display) LCD de 16 carácteres por 2 filas. Aquí utilicé el driver incluído en Arduino. Lo interesante es qué, cómo, cuándo y dónde imprimo, y el uso que le dí a luz de fondo (backlight), ya que ésta se enciende y se apaga después de un tiempo, y también parpadea al ritmo que nosotros queramos durante las veces que queramos.
Teclado
Con el fin de ahorrar terminales (y porque en verdad no había de otra) utilicé un teclado basado en escalera analógica. Esto nos permite utilizar una sola terminal para obtener 5 teclas.
Salida
Para la salida utilicé un relevador de 127V y 10A.
Máquina de estados
La inteligencia de este proyecto no radica únicamente en el hardware, sino en la máquina de estados finita que gobierna el comportamiento del sistema mientras está en funcionamiento. Así que antes de ver la programación echémosle un ojo a los estados por los que pasa el sistema.
En espera (Waiting)
Este es el estado normal del sistema y sale de aquí debido a uno de dos eventos:
- El sensor inferior del tinaco indica que éste se vació. La bomba y el temporizador regresivo se activan y establece que el siguiente estado es Llenando el tinaco. ¡La cisterna debe tener agua!
- Los sensores superior e inferior se intercambiaron. Avisa al usuario con un mensaje y la luz de fondo de la pantalla parpadea. Se mantiene en este estado hasta que los sensores hayan regresado a (o se hayan puesto en) su lugar correcto.
Llenando el tinaco (Filling the upper tank)
Este es el estado en que el tinaco se está llenando con agua de la cisterna. El sistema se mantendrá en este estado hasta que suceda uno de tres eventos:
- El tinaco se llenó antes de que el tiempo expirara. Un temporizador de unos pocos segundos es activado y establece que el siguiente estado es Retardo al terminar. Esto es para evitar falsos positivos en el llenado, como ya lo mencioné, y como abordaré más adelante. Nota que la bomba sigue activa.
- La cisterna se vació antes de llenar el tinaco y antes de que el tiempo expirara. La bomba y el temporizador son desactivados y establece que el siguiente estado es Llenando la cisterna.
- El tiempo expiró antes de que el tinaco se llenara. Avisa al usuario con un mensaje y la luz de fondo de la pantalla parpadea y establece que el siguiente estado es Tiempo terminado. La única forma de salir de este estado es reiniciando al sistema. Si el tiempo expiró es por una razón y debemos conocer el porqué antes de volver a activar el sistema nuevamente.
Llenando la cisterna (Filling the lower tank)
En este estado la cisterna se está llenando con agua de la calle (así le decimos en México; el término correcto es “se está llenando con el suministro de agua municipal”). Hay dos formas de salir de este estado:
- Esperar a que la cisterna se llene. Una vez que se ha llenado se activa la bomba y el temporizador y establece que el siguiente estado es Llenando el tinaco, es decir, regresa a terminar su trabajo.
- Con un reinicio del sistema en caso de que no haya agua y la cisterna no pueda llenarse. En este caso el sistema comenzará en el estado En espera.
Retardo al terminar (Delay after)
Cuando el sensor superior del tinaco detecta agua significa que ya se llenó; sin embargo, dado que el agua está cayendo con fuerza gracias a la presión de la bomba, el líquido está turbio y formando olas que pueden alcanzar al sensor un poco antes de que realmente se haya llenado. Este estado deja pasar algunos segundos antes de desactivar la bomba y termina cuando uno de dos eventos se presenta:
- El tiempo expiró. Desactiva la bomba, pone un mensaje en la pantalla, y establece el siguiente estado en En espera.
- La cisterna se vació. Realiza los mismos pasos que en 1; la idea aquí es que la bomba no trabaje sin agua, aún cuando sean unos pocos segundos.
Sensores intercambiados (Swapped sensors)
Durante la instalación uno podría cometer el error de intercambiar la posición de los sensores en uno o ambos tanques. O quizás, debido al fenómeno de la corrosión, el sensor inferior podría dejar de detectar agua, aunque esté cubierto de líquido, ¡y es ilógico que el sensor superior indique la presencia de agua, pero el inferior no!
Este estado detecta la situación descrita. Para salir es necesario poner a los sensores en su lugar correcto, o darle mantenimiento al sensor inferior en caso de que hubiese sido víctima de la corrosión.
Tiempo terminado (Time over)
El sistema llega a este estado desde el estado Llenando el tinaco cuando el tinaco no termina de llenarse antes de que el tiempo expire. La única forma de salir de aquí es reiniciando al sistema.
Parada de emergencia (Emergency stop)
Todo sistema electrónico que involucre el control de equipo eléctrico o delicado debe tener una forma de detenerse en caso de que algo malo suceda. Pues este proyecto no es la excepción. La tecla Back es la encargada de hacer las veces del botón de paro de emergencia. Cuando esta condición se presenta, el sistema avisa al usuario con un mensaje y la luz de fondo de la pantalla parpadea. La única forma de salir de aquí es reiniciando al sistema.
Ten en cuenta que tú puedes modificar el comportamiento de los estados e inclusive agregar los tuyos. Estos que programé los pensé para abarcar la mayoría de las situaciones, pero la cabeza de cada programador es un mundo. ¡De tarea puedes programar modos manual y automático!
Software
El código lo desarrollé en C++ (está de más decirlo cuando es de todos sabido que Arduino compila con C++) pero hago el enfásis ya que escribí muchas clases, algunas de las cuales (la verdad, todas) me gustaron mucho, no tanto por su complejidad, ya que en realidad son simples y fáciles de entender, sino por su forma de interactuar con el súper-loop de Arduino (el cual odio, ya lo había dicho) y por su utilidad en futuros proyectos.
Antes de describir las partes más importantes del software tengo la obligación de indicarte dos cosas:
- El armado, programación y uso de este proyecto está bajo tu total responsabilidad.
- Notarás que aunque la plataforma que utilicé es Arduino, el estilo de programación quizás sea muy diferente al que estás acostumbrado. En particular no uso variables globales, a menos que sea estrictamente necesario, y de éstas solamenté utilicé como 5, ligadas todas al hardware. Por otro lado tuve que hacer malabares para tener varias bases de tiempo, pero me gustó lo que hice (y me permito insistir, odio el súper-loop de Arduino).
El sketch completo lo puedes descargar desde aquí y la versión de desarrollo desde aquí.
Sensores
El punto principal del proyecto es sensar los niveles de agua en los dos contenedores. La idea general es tener un cable con 5V que sirva como común a los sensores superior e inferior en un contenedor. Cuando el agua cubra al común y un cable de señal, éste recibirá los 5V gracias a la conductividad del agua (la cual es poca, pero suficiente) y los mandará a la tarjeta. En caso contrario, el agua está por debajo del común y la señal, no habrá tensión, y gracias a la resistencia de pull-down en el cable de señal, la tarjeta recibirá 0V. Simple, ¿cierto?
El pinout de los sensores lo definí con una enumeración:
enum eSensors { UPPER_TANK_TOP = 15, UPPER_TANK_BOTTOM = 16, LOWER_TANK_TOP = 17, LOWER_TANK_BOTTOM = 18, SENSORS_COMMON = 19, };
Bueno, esta solución tiene una desventaja la cual es la corrosión de los cables debido a la circulación de corriente entre ellos. Y no hay forma de evitarla, pero sí de retrasarla. Y lo que hice es mi principal aporte en este proyecto.
En lugar de dejar los 5V de manera contínua en el cable común estoy mandando pulsos. Esto es, el común está a 0V durante 5 segundos, y luego mando la señal de 5V durante unos 5 mili segundos, lo estrictamente necesario para leer su estado.
Dado que el común se mantiene prácticamente todo el tiempo a 0V y los cables de señal están también a 0V (gracias a las resistencias de pull-down), entonces prácticamente no existe circulación de corriente entre ellos, retrasando el proceso de corrosión.
void read_sensors( Inputs& inputs ) { digitalWrite( SENSORS_COMMON, HIGH ); delay( 1 ); inputs.upper_tank_top = digitalRead( UPPER_TANK_TOP ); inputs.upper_tank_bottom = digitalRead( UPPER_TANK_BOTTOM ); inputs.lower_tank_top = digitalRead( LOWER_TANK_TOP ); inputs.lower_tank_bottom = digitalRead( LOWER_TANK_BOTTOM ); digitalWrite( SENSORS_COMMON, LOW ); }
Inversión de los sensores
¿Qué tal si mientras estás realizando la instalación colocas el cable de señal superior en la parte inferior del contenedor, y viceversa? ¿O qué tal que el cable de señal inferior se ha corroído? Bueno, ¡el sistema te lo hará saber!
Si estás haciendo la instalación y el sistema te avisa de cables volteados, entonces basta con que los intercambies.
Si el sistema fue instalado correctamente y ya ha pasado algo de tiempo, entonces el aviso de cables volteados significa que es momento de darle mantenimiento a los cables de señal. ¡Te dije que el sistema era inteligente!
case eStates::WAITING: if( ( inputs.upper_tank_top == HIGH and inputs.upper_tank_bottom == LOW ) or ( inputs.lower_tank_top == HIGH and inputs.lower_tank_bottom == LOW ) ) { state = eStates::SENSORS_SWAPPED; digitalWrite( WATER_PUMP, LOW ); timer.stop(); lcd_backlight.stop(); lcd_backlight.set( Blink::eMode::FOREVER, MS_TO_TICKS( 500 ), MS_TO_TICKS( 500 ) ); lcd_backlight.start(); error = true; lcd.clear(); lcd.print( "SENSORS SWAPPED" ); lcd.setCursor( 0, 1 ); lcd.print( " Verify them!" ); } else if( ... ) { ... }
Temporizador
Algunas bombas de agua indican que no deben trabajar por más de una cierta cantidad de minutos (las que conozco, 15). Aquí hay de dos: el tinaco se llena antes de ese tiempo, o te sientas a esperar que el tiempo se cumpla para desconectar de forma manual la energía eléctrica.
Bueno, de hecho hay otra alternativa: que el sistema incluyese un temporizador por software… ¡programable! Si el tinaco no se llenó en ese tiempo, entonces la bomba es desconectada de forma automática, sin que tengas que estar al pendiente.
Y eso es lo que me pasaba con un sistema previo al que te estoy presentando. Cada vez que dicho sistema se arrancaba yo tenía que poner un temporizador de 6 minutos en mi teléfono (ahorita les digo la razón de utilizar un temporizador). El llenado de mi tinaco (de 1100 litros) dura 6 minutos (porque no se vacía ya que ahorro mucha agua) y si llegase a durar más es debido a un error… el cual no ha sucedido con este sistema.
Peeeeero con otro sistema que hice ya hace muchos años (basado en los HCS08 de Freescale) una vez sucedió algo raro: durante una tormenta eléctrica la bomba se activó después de un rayo muy fuerte, y debido al ruido de la lluvia, no me dí cuenta sino hasta dos horas después. El flotador del tinaco evitó que el agua se cayera, sin embargo, la bomba estuvo tratando de subir agua todo ese tiempo. Afortunadamente no le pasó nada, y aún la seguimos usando. Moraleja: “Agrega un temporizador a tu sistema”.
Y esa es la razón por la cual agregué el temporizador. Pero como 6 minutos me sirven perfecto a mí, pero a tí quizás no, entonces agregué al sistema una forma para que tú puedas establecer el tiempo de trabajo de tu bomba, luego de lo cual se detendrá. Y por si eso no fuera poco, el tiempo programado queda guardado en la memoria EEPROM del chip ATmega328. ¡El sistema es inteligente!
case eStates::WAITING: if( ... ) { ... } else if( inputs.upper_tank_bottom == LOW and inputs.lower_tank_bottom == HIGH ) { state = eStates::UPPER_TANK_FILLING; digitalWrite( WATER_PUMP, HIGH ); timer.set( downTimer_set.minutes, downTimer_set.seconds, true ); // <--- ¡TEMPORIZADOR! lcd_backlight.stop(); lcd_backlight.set( Blink::eMode::ONCE, MS_TO_TICKS( 10000 ) ); lcd_backlight.start(); print_text( 6, 0, "FIL UP TNK" ); }
Pantalla LCD
Simplemente porque podemos es que le agregué una pantalla LCD. En ella muestro información importante como:
- El estado de los sensores.
- El estado del sistema.
- La cuenta del temporizador regresivo (cuando el sistema está activo).
Las pantallas LCD incluyen una luz de fondo LED (backlight LED), y aunque es posible tenerla encendida el 100% del tiempo, ni es conveniente ni aporta nada, además que reduce la vida útil del LED. Así que incluí que solamente se encendiera, por unos cuantos segundos, bajo ciertas circunstancias:
- Se presionó una tecla.
- Hubo un cambio de estado.
- Hubo un error:
- El temporizador llegó a cero antes que el tinaco se llenara (time over).
- Inversión de los cables de señal (sensors swapped).
- Se activó el paro de emergencia.
Rebote del agua
Finalmente, también programé al sistema de tal manera (estado eState::DELAY_AFTER) que cuando recibe la señal de llenado deje activa la bomba por unos segundos más. La razón es que el agua cae a presión dentro del tinaco y forma “olas”. Una de estas olas podría alcanzar al sensor superior un poco antes de alcanzar el llenado completo y avisar, erróneamente, que se llenó. Piénsalo como si fuese “ruido”. Y personalmente me molesta ver, en la pantalla, que el estado del sensor superior se activa y desactiva. Con el tiempo extra logramos estabilizar al dicho sensor, y eso me hace feliz.
En el siguiente fragmento de código podemos ver que luego de que el sensor superior del tinaco detecta el agua, pone al temporizador y salta al estado eStateS::DELAY_AFTER.
case eStates::UPPER_TANK_FILLING: { if( ... ) { ... } else if( inputs.upper_tank_top == HIGH ) { state = eStates::DELAY_AFTER; timer.stop(); timer_after.set( 0, 5, true ); // <-- 5 segundos lcd_backlight.stop(); lcd_backlight.set( Blink::eMode::ONCE, MS_TO_TICKS( 10000 ) ); lcd_backlight.start(); buzzer.set( Blink::eMode::ONCE, MS_TO_TICKS( 300 ) ); buzzer.start(); print_text( 6, 0, " DONE! " ); } else if( ... )
Resumen
Aunque a lo largo del tiempo he realizado varios sistemas Tinaco-Cisterna, algunos con muy buenos resultados, el que te presenté en esta ocasión había estado rondando mi cabeza por mucho tiempo, principalmente por la característica del temporizador (y de la pantalla LCD, debo admitirlo).
La máquina de estados también ha evolucionado, tanto en el número de estados, como en las transiciones y acciones. Cada vez se ha hecho más sofisticada.
Del lado de los sensores prefiero los eléctricos y los mecánicos. Pero es fácil que tú los intercambies por sensores ultrasónicos, luminosos o barométricos. Es cuestión de gustos y hacia dónde orientemos la aplicación final.
Aunque este proyecto lo pude programar utilizando un sistema operativo de tiempo real, como FreeRTOS y mi proyecto KleOS, decidí que se quedara en un sketch de Arduino debido a que son pocos los programadores familiarizados con su uso y me gustaría que fuera utilizado por muchos de ustedes, ingenieros y aficionados a la Electrónica. Pero si tú sabes utilizarlo, o conoces alguno otro, ¡adelante, pórtalo!
Espero que este artículo te sea útil. Si quieres más artículos como éste, podrías considerar suscribirte al blog.
¿Necesitas alguien que te diseñe un circuito impreso de manera profesional en KiCAD? Búscame en Fiverr. ¡Diseños desde 30USD!
¿Ya conoces mi curso gratuito de Arduino en tiempo real, utilizando el Arduino UNO o el Arduino Due y FreeRTOS?
¿Te gustaría ser de los primeros en recibir las actualizaciones a mi blog? ¡Suscríbete, es gratis!
- Esta clase poco conocida de Arduino le dará vida a tus displays de texto y te olvidarás de tener que escribir funciones de conversión - septiembre 20, 2024
- Printable: The class you didn’t know existed in Arduino and that you won’t be able to stop using - septiembre 1, 2024
- Printable: La clase que no sabías que existía en Arduino y que no podrás dejar de usar - agosto 3, 2024
4 COMENTARIOS