

Hoy vamos a hablar sobre un problema común que muchos enfrentan: cómo hacer trabajar bombas de agua de manera alternada. Como sabemos, tradicionalmente existen dos métodos para lograrlo: por temporizadores o mediante sensores de nivel. Aunque ambos funcionan adecuadamente, la realidad es que presentan algunos inconvenientes significativos. Por un lado, su instalación y el cableado es bastante laboriosa, llegando a tomar aproximadamente 6 horas . Por otro lado, estos métodos suelen incrementar considerablemente el costo de los componentes necesarios.
Sin embargo, ¿qué pasaría si pudiéramos crear una solución más eficiente? Es por eso que hemos desarrollado una tarjeta de control unificada que no solo simplifica todo el proceso, sino que además puede gestionar ambos tipos de control simultáneamente. De hecho, esta tarjeta va más allá, ya que también puede leer sensores de nivel de agua mientras mantiene las funciones manuales y automáticas tradicionales. Además de esto, permite seleccionar las bombas individualmente y cuenta con una intuitiva pantalla con pulsadores para configurar fácilmente tanto los tiempos como los modos de operación.
Si te interesa conocer paso a paso cómo hemos desarrollado este proyecto profesional, desde el diseño del esquemático electrónico hasta la implementación de la máscara personalizada, entonces no te despegues de este articulo.
DATOS TÉCNICOS
LISTA DE MATERIALES
| Quantity | Comment | Designator | Footprint | Value | Manufacturer Part | Manufacturer | Supplier Part |
| 1 | BS-2-1 | B1 | BAT-TH_BS-2-1 | BS-2-1 | Q&J | C70376 | |
| 1 | 3kHz | BUZZER1 | BUZ-TH_BD9.0-P5.00-D0.6-FD | 3kHz | HNB09A03 | 华能 | C96102 |
| 9 | 100nF | C1,C3,C4,C5,C9,C10,C13,C15,C17 | C0603 | 100nF | CC0603KRX7R9BB104 | YAGEO(国巨) | C14663 |
| 2 | 10uF | C2,C11 | CAP-SMD_L3.2-W1.6-RD-C7171 | 10uF | TAJA106K016RNJ | Kyocera AVX | C7171 |
| 2 | 22pF | C6,C7 | C0603 | 22pF | CL10C220JB8NNNC | SAMSUNG(三星) | C1653 |
| 3 | 47uF | C8,C12,C14 | C1206 | 47uF | CL31A476MPHNNNE | SAMSUNG(三星) | dC96123 |
| 1 | 470uF | C16 | CAP-SMD_BD8.0-L8.3-W8.3-LS8.9-FD | 470uF | CK1C471M-CRF10 | ROQANG(容强) | C5162359 |
| 2 | WJ2EDGR-5.08-3P | CN1,CN2 | CONN-TH_3P-P5.08_WJ2EDGR-5.08-3P | WJ2EDGR-5.08-3P | KANGNEX(康奈克斯电气) | C70914 | |
| 1 | WJ2EDGR-5.08-2P | CN3 | CONN-TH_WJ2EDGR-5.08-2P | WJ2EDGR-5.08-2P | KANGNEX(康奈克斯电气) | C8383 | |
| 4 | 1N5819WS | D1,D7,D8,D9 | SOD-323_L1.8-W1.3-LS2.5-RD | 1N5819WS | Hottech(合科泰) | C191023 | |
| 5 | 1N4148WS | D2,D3,D4,D5,D6 | SOD-323_L1.8-W1.3-LS2.5-RD | 1N4148WS | CJ(江苏长电/长晶) | C2128 | |
| 1 | 2010T1A250V | F1 | FUSE-TH_L8.5-W4.0-P5.08-D0.6 | 2010T1A250V | WALTER(华德) | C354900 | |
| 1 | 2.54-1x6P直针 | H1 | HDR-TH_6P-P2.54-V-M-1 | 2.54-1x6P直针 | BOOMELE(博穆精密) | C37208 | |
| 1 | LCD-16X2 | LCD1 | LCD-16X2 | ||||
| 5 | XL-302UGD | LED1,LED2,LED3,LED7,LED8 | LED-TH_BD3.8-P2.54-FD_GREEN | XL-302UGD | XINGLIGHT(成兴光) | C2895476 | |
| 3 | XL-302SURD | LED4,LED5,LED6 | LED-TH_BD3.8-P2.54-RD | XL-302SURD | XINGLIGHT(成兴光) | C2895470 | |
| 7 | MMBT3904 | Q1,Q2,Q3,Q4,Q5,Q6,Q7 | SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR | MMBT3904 | CJ(江苏长电/长晶) | C20526 | |
| 1 | 200Ω | R1 | R0805 | 200Ω | 0805W8F2000T5E | UNI-ROYAL(厚声) | C17540 |
| 3 | 4.7kΩ | R2,R3,R4 | R0603 | 4.7kΩ | 0603WAF4701T5E | UNI-ROYAL(厚声) | C23162 |
| 1 | 510kΩ | R5 | R0603 | 510kΩ | 0603WAF5103T5E | UNI-ROYAL(厚声) | C23192 |
| 11 | 10kΩ | R6,R16,R21,R24,R25,R30,R31, R34,R37,R40,R43 |
R0603 | 10kΩ | 0603WAF1002T5E | UNI-ROYAL(厚声) | C25804 |
| 1 | 1.5MΩ | R7 | R0603 | 1.5MΩ | 0603WAF1504T5E | UNI-ROYAL(厚声) | C4172 |
| 1 | 1MΩ | R8 | R0603 | 1MΩ | 0603WAF1004T5E | UNI-ROYAL(厚声) | C22935 |
| 11 | 330Ω | R9,R10,R11,R12,R13,R14,R19,R26,R27,R35,R38 | R0805 | 330Ω | 0805W8F3300T5E | UNI-ROYAL(厚声) | C17630 |
| 6 | 47kΩ | R15,R17,R29,R32,R39,R41 | R1206 | 47kΩ | 1206W4F4702T5E | UNI-ROYAL(厚声) | C25833 |
| 7 | 1kΩ | R18,R20,R23,R28,R33,R36,R42 | R0603 | 1kΩ | 0603WAF1001T5E | UNI-ROYAL(厚声) | C21190 |
| 1 | 10kΩ | R22 | RES-ADJ-SMD_3P-L3.0-W3.8-P1.75-BR | 10kΩ | TC33X-2-103E | BOURNS | C719176 |
| 3 | G5NB-1A-E-DC5V | RLY1,RLY2,RLY3 | RELAY-TH_G5NB-1A-E-DCXX | G5NB-1A-E-DC5V | OMRON(欧姆龙) | C48746 | |
| 10 | TS665CJ | SW1,SW2,SW3,SW4,SW5, SW6,SW7,SW8,SW9,SW10 |
SW-TH_4P-L6.0-W6.0-P4.50-LS6.5 | TS665CJ | SHOU HAN(首韩) | C393938 | |
| 1 | ATMEGA328P-AU | U1 | TQFP-32_L7.0-W7.0-P0.80-LS9.0-BL | ATMEGA328P-AU | MICROCHIP(美国微芯) | C14877 | |
| 1 | DS1307ZN+T&R | U2 | SOP-8_L4.9-W3.9-P1.27-LS6.0-BL | DS1307ZN+T&R | ADI(亚德诺)/MAXIM(美信) | C26858 | |
| 1 | AT24C02 | U3 | SOP-8_L4.9-W3.9-P1.27-LS6.0-BL | AT24C02 | HXY MOSFET(华轩阳电子) | C7470930 | |
| 6 | EL357N(C)(TA)-G | U4,U6,U8,U9,U10,U11 | OPTO-SMD-4_L4.4-W4.1-P2.54-LS7.0-TL | EL357N(C)(TA)-G | EVERLIGHT(亿光) | C42379244 | |
| 1 | ULN2003ADR | U5 | SOIC-16_L9.9-W3.9-P1.27-LS6.0-BL | ULN2003ADR | TI(德州仪器) | C7512 | |
| 1 | PCF8574T/TR | U7 | SO-16_L10.3-W7.5-P1.27-LS10.3-BL | PCF8574T/TR | HGSEMI(华冠) | C2987288 | |
| 1 | TAS5-5-WEDT | U12 | PWRM-TH_TAS5-X-WEDT | TAS5-5-WEDT | TDPOWER(腾达电源) | C2687651 | |
| 1 | 10D561K | VR1 | RES-TH_L13.0-W6.0-P7.50-D0.9-S6.00 | 10D561K | HEL(鸿志) | C113236 | |
| 1 | 32.768kHz | X1 | FC-135R_L3.2-W1.5 | 32.768kHz | Q13FC13500004 | EPSON(爱普生) | C32346 |
| 1 | 16MHz | X2 | CRYSTAL-SMD_4P-L3.2-W2.5-BL | 16MHz | TAXM16M4RLBCCT2T | YJX(雅晶鑫) | C164049 |
CODIGO
#include <EEPROM.h>
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 rtc;
char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
//PINES DE ENTRADA DE SENSORES DE NIVEL DE AGUA
const byte sensor_poso = 4; // Sensor poso
const byte sensor_tanque = 3; // Sensor tanque
const byte entrada_auxiliar = 2; //auxiliar
//PINES DE SALIDA PARA CONTROLAR LOS LEDS
const byte led_auto = 11; // Led Auto
const byte led_manual = 12; // Led Manual
//PINES DE SALIDA PARA CONTROLAR LOS MOTORES
const byte motor1 = 14; // motor1
const byte motor2 = 15; // motor2
const byte motor3 = 16; //motor3
//PINES DE SALIDA PARA Buzzer
const byte Buzzer = 13; //Buzzer
// Configuración de buzzer
const int tonoTecla = 1000; // Frecuencia en Hz para pulsación normal
const int tonoGuardado = 2000; // Frecuencia en Hz para guardado
const int duracionCorta = 50; // Duración en ms para tecla normal
const int duracionLarga = 200; // Duración en ms para guardado
////////////////////////DIRECCION PARA ALMACENAR EN LA MENORIA EEPROM
const byte direc_control = 0; //
const byte direc_motores = 3; // Direccion EEPROM para almacenar los valores de total Motores
const byte direc_mode = 6;
bool act_sensonrs = false;
bool control = false;
byte contador_Motores_M = 2; // cantidad de motores activas (mínimo 2, máximo 3)
byte mode = 0;
byte motors = 0;
byte read_sensor_tanque = 1;
struct ConfigMotor {
byte horas;
byte minutos;
byte segundos;
};
ConfigMotor motores[3] = {
{ 0, 0, 0 }, // Motor 1
{ 0, 0, 0 }, // Motor 2
{ 0, 0, 0 } // Motor 3
};
const int dirEEPROM[3] = { 60, 70, 80 }; // Direcciones base EEPROM para cada motor
// Variables para almacenar hora y fecha
byte hora, minuto, segundo, dia, mes, anio = 0;
unsigned long tiempoAnterior = 0;
// ====== VARIABLES GLOBALES ADICIONALES ======
byte motorActivo = 255; // 255=ninguno, 0=motor1, 1=motor2, 2=motor3
DateTime horaInicioMotor;
const byte pinesMotores[3] = { motor1, motor2, motor3 };
void cargarConfigMotores() {
for (int m = 0; m < 3; m++) {
EEPROM.get(dirEEPROM[m], motores[m]);
// Si es la primera lectura (valores en 255)
if (motores[m].horas == 255) motores[m] = { 0, 0, 0 };
}
}
void setup() {
Serial.begin(9600); // Iniciar comunicación serial para depuración
Serial.println("Sistema iniciado");
rtc.begin();
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // solo activa esta linea para ajustar la hora y la fecha, luego bloquealo
EEPROM.get(direc_motores, contador_Motores_M);
control = EEPROM.read(direc_control);
EEPROM.get(direc_mode, mode);
cargarConfigMotores();
pinMode(sensor_poso, INPUT);
pinMode(sensor_tanque, INPUT);
pinMode(entrada_auxiliar, INPUT);
attachInterrupt(digitalPinToInterrupt(sensor_tanque), tanque, FALLING);
pinMode(led_auto, OUTPUT);
pinMode(led_manual, OUTPUT);
pinMode(motor1, OUTPUT);
pinMode(motor2, OUTPUT);
pinMode(motor3, OUTPUT);
pinMode(Buzzer, OUTPUT);
// Mostrar configuración inicial
mostrarEstadoActual();
}
void loop() {
// Verificar sensores y controlar motores según el modo
if (digitalRead(sensor_poso) == LOW) act_sensonrs = true;
else {
digitalWrite(motor1, LOW);
digitalWrite(motor2, LOW);
digitalWrite(motor3, LOW);
act_sensonrs = false;
}
switch (mode) {
case 0:
digitalWrite(led_auto, LOW);
digitalWrite(led_manual, LOW);
digitalWrite(motor1, LOW);
digitalWrite(motor2, LOW);
digitalWrite(motor3, LOW);
break;
case 1:
digitalWrite(led_auto, HIGH);
digitalWrite(led_manual, LOW);
break;
case 2:
digitalWrite(led_auto, LOW);
digitalWrite(led_manual, HIGH);
break;
}
if (mode == 2) {
switch (motors) {
case 0:
digitalWrite(motor1, LOW);
digitalWrite(motor2, LOW);
digitalWrite(motor3, LOW);
break;
case 1:
digitalWrite(motor1, HIGH);
digitalWrite(motor2, LOW);
digitalWrite(motor3, LOW);
break;
case 2:
digitalWrite(motor1, LOW);
digitalWrite(motor2, HIGH);
digitalWrite(motor3, LOW);
break;
case 3:
digitalWrite(motor1, LOW);
digitalWrite(motor2, LOW);
digitalWrite(motor3, HIGH);
break;
}
} else {
motors = 0;
}
if (mode == 1) {
if (control == false && act_sensonrs == true) {
if (digitalRead(sensor_tanque) == LOW) {
if (read_sensor_tanque == 1) {
digitalWrite(motor1, HIGH);
digitalWrite(motor2, LOW);
}
if (read_sensor_tanque == 2) {
digitalWrite(motor1, LOW);
digitalWrite(motor2, HIGH);
}
} else {
digitalWrite(motor1, LOW);
digitalWrite(motor2, LOW);
}
} else if (control == true && act_sensonrs == true) {
controlarMotoresPorTurnos();
}
}
// Verificar comunicación serial para comandos
if (Serial.available() > 0) {
procesarComandoSerial();
}
// Mostrar estado cada cierto tiempo
static unsigned long ultimoTiempoMostrado = 0;
if (millis() - ultimoTiempoMostrado > 5000) { // Cada 5 segundos
mostrarEstadoActual();
ultimoTiempoMostrado = millis();
}
}
void procesarComandoSerial() {
String comando = Serial.readStringUntil('\n');
comando.trim();
if (comando == "status") {
mostrarEstadoActual();
}
else if (comando == "mode0") {
mode = 0;
EEPROM.put(direc_mode, mode);
Serial.println("Modo 0: Apagado");
sonidoGuardado();
}
else if (comando == "mode1") {
mode = 1;
EEPROM.put(direc_mode, mode);
Serial.println("Modo 1: Auto");
sonidoGuardado();
}
else if (comando == "mode2") {
mode = 2;
EEPROM.put(direc_mode, mode);
Serial.println("Modo 2: Manual");
sonidoGuardado();
}
else if (comando == "motor1") {
motors = 1;
Serial.println("Motor 1 activado");
sonidoTecla();
}
else if (comando == "motor2") {
motors = 2;
Serial.println("Motor 2 activado");
sonidoTecla();
}
else if (comando == "motor3") {
motors = 3;
Serial.println("Motor 3 activado");
sonidoTecla();
}
else if (comando == "motoroff") {
motors = 0;
Serial.println("Motores apagados");
sonidoTecla();
}
else if (comando == "control_sensor") {
control = false;
EEPROM.update(direc_control, control);
Serial.println("Control por sensores activado");
sonidoGuardado();
}
else if (comando == "control_tiempo") {
control = true;
EEPROM.update(direc_control, control);
Serial.println("Control por tiempos activado");
sonidoGuardado();
}
else if (comando.startsWith("motores ")) {
int cantidad = comando.substring(8).toInt();
if (cantidad >= 2 && cantidad <= 3) {
contador_Motores_M = cantidad;
EEPROM.put(direc_motores, contador_Motores_M);
Serial.print("Cantidad de motores: ");
Serial.println(contador_Motores_M);
sonidoGuardado();
} else {
Serial.println("Error: cantidad debe ser 2 o 3");
}
}
else if (comando.startsWith("config_motor")) {
// Formato: config_motor,motor#,horas,minutos,segundos
// Ejemplo: config_motor,1,0,30,0
int posiciones[4];
int parametro = 0;
int inicio = 12; // Posición después de "config_motor,"
for (int i = inicio; i < comando.length(); i++) {
if (comando.charAt(i) == ',') {
posiciones[parametro] = comando.substring(inicio, i).toInt();
inicio = i + 1;
parametro++;
}
}
// Último valor
if (parametro < 4) {
posiciones[parametro] = comando.substring(inicio).toInt();
parametro++;
}
if (parametro == 4) {
int motor = posiciones[0] - 1; // Motor 1 sería índice 0
if (motor >= 0 && motor < 3) {
motores[motor].horas = posiciones[1];
motores[motor].minutos = posiciones[2];
motores[motor].segundos = posiciones[3];
guardarConfigMotor(motor);
Serial.print("Motor ");
Serial.print(motor + 1);
Serial.print(" configurado: ");
Serial.print(motores[motor].horas);
Serial.print(":");
Serial.print(motores[motor].minutos);
Serial.print(":");
Serial.println(motores[motor].segundos);
sonidoGuardado();
} else {
Serial.println("Error: Motor debe ser 1, 2 o 3");
}
} else {
Serial.println("Error: formato debe ser config_motor,motor#,horas,minutos,segundos");
}
}
else if (comando.startsWith("set_time")) {
// Formato: set_time,hora,minuto,segundo,dia,mes,año
int valores[6];
int parametro = 0;
int inicio = 9; // Posición después de "set_time,"
for (int i = inicio; i < comando.length(); i++) {
if (comando.charAt(i) == ',') {
valores[parametro] = comando.substring(inicio, i).toInt();
inicio = i + 1;
parametro++;
}
}
// Último valor
if (parametro < 6) {
valores[parametro] = comando.substring(inicio).toInt();
parametro++;
}
if (parametro == 6) {
rtc.adjust(DateTime(2000 + valores[5], valores[4], valores[3], valores[0], valores[1], valores[2]));
Serial.println("Fecha y hora actualizadas");
sonidoGuardado();
} else {
Serial.println("Error: formato debe ser set_time,hora,minuto,segundo,dia,mes,año");
}
}
else if (comando == "help") {
mostrarAyuda();
}
else {
Serial.println("Comando desconocido. Escribe 'help' para ver los comandos disponibles.");
}
}
void mostrarEstadoActual() {
DateTime now = rtc.now();
Serial.println("==== ESTADO ACTUAL ====");
Serial.print("Fecha: ");
Serial.print(now.day());
Serial.print("/");
Serial.print(now.month());
Serial.print("/");
Serial.println(now.year());
Serial.print("Hora: ");
Serial.print(now.hour());
Serial.print(":");
if (now.minute() < 10) Serial.print("0");
Serial.print(now.minute());
Serial.print(":");
if (now.second() < 10) Serial.print("0");
Serial.println(now.second());
Serial.print("Modo: ");
switch (mode) {
case 0: Serial.println("Apagado"); break;
case 1: Serial.println("Automático"); break;
case 2: Serial.println("Manual"); break;
}
Serial.print("Control: ");
Serial.println(control ? "Por tiempos" : "Por sensores");
Serial.print("Nivel agua pozo: ");
Serial.println(digitalRead(sensor_poso) == LOW ? "Disponible" : "No disponible");
Serial.print("Sensor tanque: ");
Serial.println(digitalRead(sensor_tanque) == LOW ? "Activo" : "Inactivo");
Serial.print("Total motores: ");
Serial.println(contador_Motores_M);
Serial.println("Tiempos motores:");
for (int i = 0; i < contador_Motores_M; i++) {
Serial.print("Motor ");
Serial.print(i + 1);
Serial.print(": ");
Serial.print(motores[i].horas);
Serial.print(":");
if (motores[i].minutos < 10) Serial.print("0");
Serial.print(motores[i].minutos);
Serial.print(":");
if (motores[i].segundos < 10) Serial.print("0");
Serial.println(motores[i].segundos);
}
Serial.println("======================");
}
void mostrarAyuda() {
Serial.println("==== COMANDOS DISPONIBLES ====");
Serial.println("status - Muestra el estado actual");
Serial.println("mode0 - Modo apagado");
Serial.println("mode1 - Modo automático");
Serial.println("mode2 - Modo manual");
Serial.println("motor1 - Activa motor 1 (solo en modo manual)");
Serial.println("motor2 - Activa motor 2 (solo en modo manual)");
Serial.println("motor3 - Activa motor 3 (solo en modo manual)");
Serial.println("motoroff - Apaga todos los motores");
Serial.println("control_sensor - Activa control por sensores");
Serial.println("control_tiempo - Activa control por tiempos");
Serial.println("motores N - Establece número de motores (2-3)");
Serial.println("config_motor,M,h,m,s - Configura tiempo del motor M");
Serial.println("set_time,h,m,s,d,M,a - Configura hora y fecha");
Serial.println("help - Muestra esta ayuda");
Serial.println("=============================");
}
void tanque() {
read_sensor_tanque++;
if (read_sensor_tanque > 2) read_sensor_tanque = 1;
}
void controlarMotoresPorTurnos() {
// Si el control está activo pero no hay motor activo, iniciar ciclo
if (control && motorActivo == 255) {
motorActivo = 0;
digitalWrite(pinesMotores[motorActivo], HIGH);
horaInicioMotor = rtc.now();
return;
}
// Si el control está inactivo pero hay motor activo, detener todo
if (!control && motorActivo != 255) {
for (byte i = 0; i < 3; i++) {
digitalWrite(pinesMotores[i], LOW);
}
motorActivo = 255;
return;
}
// Si hay motor activo y control activo, verificar tiempo
if (control && motorActivo != 255) {
DateTime ahora = rtc.now();
unsigned long segundosTranscurridos = ahora.unixtime() - horaInicioMotor.unixtime();
unsigned long segundosProgramados = motores[motorActivo].horas * 3600UL + motores[motorActivo].minutos * 60UL + motores[motorActivo].segundos;
if (segundosTranscurridos >= segundosProgramados) {
// Apagar motor actual
digitalWrite(pinesMotores[motorActivo], LOW);
// Pasar al siguiente motor (solo los configurados)
motorActivo = (motorActivo + 1) % contador_Motores_M;
// Encender nuevo motor
digitalWrite(pinesMotores[motorActivo], HIGH);
horaInicioMotor = ahora;
}
}
}
void guardarConfigMotor(byte numMotor) {
EEPROM.put(dirEEPROM[numMotor], motores[numMotor]);
}
void sonidoTecla() {
tone(Buzzer, tonoTecla, duracionCorta);
delay(duracionCorta); // Pequeña pausa para evitar solapamientos
}
void sonidoGuardado() {
tone(Buzzer, tonoGuardado, duracionLarga);
delay(duracionLarga); // Pausa más larga para el sonido de confirmación
}