Caso práctico: contador de aforo Arduino UNO

Caso práctico: contador de aforo Arduino UNO — hero

Objetivo y caso de uso

Lo que construirás: Un contador de visitantes en una puerta usando un Arduino UNO R3, dos sensores IR de barrera óptica y una pantalla TM1637 de 4 dígitos. El sistema detecta entrada frente a salida midiendo qué haz se interrumpe primero y luego actualiza el conteo de ocupación en vivo en la pantalla con una respuesta casi instantánea, normalmente inferior a 100–200 ms por evento.

Por qué importa / Casos de uso

  • Seguimiento de ocupación en aulas o laboratorios: estima cuántas personas hay dentro de una sala montando los haces a través de una puerta estrecha o de un pequeño modelo de marco de puerta.
  • Monitoreo de acceso a talleres: muestra un conteo rápido en vivo en la entrada de un makerspace para que los estudiantes puedan juzgar si la sala está llena antes de entrar.
  • Conteo en stands de eventos temporales: mide el tráfico peatonal aproximado contando entradas y salidas durante demostraciones escolares o exposiciones.
  • Supervisión de salas de almacenamiento o de equipos: monitorea si un área restringida está ocupada sin cámaras, captura de imágenes ni procesamiento de video en red.
  • Práctica de sistemas embebidos: aprende detección de dirección, manejo de rebote, separación de sensores, ventanas temporales y actualizaciones simples de pantalla en tiempo real en un microcontrolador de 8 bits usando potencia mínima y 0% de GPU.

Resultado esperado

  • La pantalla TM1637 de 4 dígitos muestra el conteo de ocupación actual y se actualiza inmediatamente después de cada paso validado.
  • El sistema aumenta el conteo cuando se activa primero el haz A y luego el haz B, y lo disminuye cuando se activa primero el haz B y luego el haz A.
  • Las activaciones falsas se reducen mediante lógica de rebote y una ventana corta de detección de dirección, por ejemplo de 300–800 ms según el ancho de la puerta y la velocidad al caminar.
  • El prototipo funciona por completo en el Arduino UNO con una capacidad de respuesta simple a nivel de bucle, con manejo de eventos efectivamente en tiempo real y sin cámara, sin canalización de FPS y sin carga de GPU.

Público: estudiantes, makers y desarrolladores principiantes de sistemas embebidos que construyen prototipos prácticos basados en sensores; Nivel: principiante a intermedio

Arquitectura/flujo: el sensor IR 1 y el sensor IR 2 envían señales digitales al Arduino UNO, que compara el orden y el tiempo de activación, valida la dirección del movimiento, actualiza una variable interna de ocupación y envía el nuevo conteo a la pantalla TM1637.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 3 tablas y 4 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este prototipo es un contador educativo de ocupación, no un dispositivo de control de acceso certificado para seguridad ni un sistema de seguridad. No debe usarse donde errores de conteo puedan crear peligros, problemas de cumplimiento legal, decisiones de salida de emergencia o consecuencias operativas críticas.

Límites específicos a tener en cuenta:

  • Está diseñado solo para uso educativo de bajo voltaje alimentado por USB.
  • No debe usarse para controlar cerraduras de puertas, portones, maquinaria industrial o sistemas de emergencia.
  • Está mejor adaptado a una persona a la vez a través de un paso estrecho; las personas muy agrupadas pueden confundir la lógica de dirección.
  • La luz solar directa o fuentes IR intensas pueden reducir la fiabilidad.
  • El conteo mostrado es una estimación basada en interrupciones de haces, no un recuento garantizado de personas.
  • Asegura todo el cableado y los soportes para que nada pueda caer en la zona de paso ni crear riesgo de tropiezo.

Requisitos previos

Antes de comenzar, deberías tener:

  • Un ordenador con acceso USB al Arduino UNO
  • Arduino CLI instalado y disponible en tu terminal
  • Comprensión básica de:
  • pines de entrada digital
  • temporización con millis()
  • carga de sketches de Arduino
  • cableado simple en protoboard
  • Un trayecto de prueba estrecho donde puedas mover una mano u objeto a través de los dos haces IR en secuencia

Preparación recomendada:

  1. Instala Arduino CLI.
  2. Confirma que el UNO aparece como un puerto serie en tu sistema.
  3. Mantén los dos pares de sensores de barrera óptica físicamente separados por una pequeña distancia, como 8 cm a 20 cm.
  4. Prueba primero con iluminación interior antes de pasar a un entorno más brillante.

Materiales

Usa la familia de dispositivos y el modelo exactos solicitados:

  • Arduino UNO R3 (ATmega328P)
  • Dos sensores IR de barrera óptica
  • Cada conjunto de sensor normalmente incluye un emisor IR y un receptor IR
  • Pantalla TM1637 de 4 dígitos
  • Protoboard
  • Cables jumper
  • Cable USB para Arduino UNO
  • Opcional:
  • Cartón, tablero de espuma o mini marco de puerta impreso en 3D
  • Cinta de doble cara o bridas para alinear sensores

Funciones sugeridas de los materiales

Pieza Función en el proyecto Notas
Arduino UNO R3 (ATmega328P) Controlador principal Ejecuta la lógica de conteo y actualiza la pantalla
Par de sensores IR de barrera A Primera posición del haz Detecta un lado del cruce
Par de sensores IR de barrera B Segunda posición del haz Detecta el segundo lado del cruce
Pantalla TM1637 de 4 dígitos Pantalla local de ocupación Muestra conteo de 0 a 9999
Protoboard y jumpers Interconexión de prototipado Mantén el cableado de sensores corto y ordenado

Configuración/Conexión

Este proyecto usa:

  • dos entradas digitales para los receptores IR
  • dos salidas digitales para la pantalla TM1637
  • líneas de 5 V y GND para todos los módulos

Asignación de pines

Usa los siguientes pines del Arduino:

  • Salida del receptor IR A -> D2
  • Salida del receptor IR B -> D3
  • TM1637 CLK -> D4
  • TM1637 DIO -> D5

Conexiones de alimentación:

  • TM1637 VCC -> 5V
  • TM1637 GND -> GND
  • Receptor IR A VCC -> 5V
  • Receptor IR A GND -> GND
  • Receptor IR B VCC -> 5V
  • Receptor IR B GND -> GND
  • Emisor IR A -> 5V y GND según requiera su módulo
  • Emisor IR B -> 5V y GND según requiera su módulo

Notas importantes de cableado

  1. Las salidas de los receptores solo van a pines de entrada del Arduino.
  2. No conectes las salidas de los sensores directamente a la pantalla.
  3. La mayoría de los receptores de barrera óptica se comportan como salidas activas en bajo.
  4. Eso significa:
    • haz intacto -> la entrada lee HIGH
    • haz interrumpido -> la entrada lee LOW
  5. El sketch de abajo asume este comportamiento común.
  6. La alineación de sensores importa más que el código.
  7. Coloca emisor y receptor enfrentados directamente.
  8. Confirma que cada haz cambie de estado de forma fiable antes de probar la lógica de dirección.
  9. La separación entre haces afecta la precisión.
  10. Comienza con 10 cm a 15 cm entre el haz A y el haz B.
  11. Demasiado cerca: la dirección puede volverse ambigua.
  12. Demasiado lejos: una persona puede detenerse entre haces y provocar tiempos de espera.
  13. Usa un paso estrecho.
  14. Esto funciona mejor cuando solo pasa una persona a la vez.

Idea práctica de montaje

Construye un prototipo educativo simple de puerta:

  1. Haz un arco de cartón o un marco rectangular.
  2. Monta el haz A en un lado y su receptor enfrente.
  3. Monta el haz B en paralelo a A, ligeramente detrás de él en la dirección de marcha.
  4. Etiqueta el lado exterior del marco y el lado interior.
  5. Define:
  6. A luego B = entrando
  7. B luego A = saliendo

Eso hace que el objeto sea inmediatamente útil como contador de entradas a una sala.

Código validado

dual_ir_beam_visitor_counter.ino

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

/*
  dual_ir_beam_visitor_counter.ino

  Target:
    Arduino UNO R3 (ATmega328P)

  Hardware:
    - 2x IR break-beam receivers on D2 and D3
    - TM1637 4-digit display on D4 (CLK) and D5 (DIO)

  Behavior:
    - Beam A then Beam B => ENTRY => count++
    - Beam B then Beam A => EXIT  => count-- (not below zero)
    - Serial output logs events for validation
    - Display shows occupancy count from 0 to 9999

  Notes:
    - Assumes receiver outputs are active-low:
        beam clear   -> HIGH
        beam broken  -> LOW
    - Uses INPUT_PULLUP for stable idle state
*/

#include <Arduino.h>

// -----------------------------
// Pin configuration
// -----------------------------
const uint8_t PIN_BEAM_A = 2;
const uint8_t PIN_BEAM_B = 3;
const uint8_t PIN_TM1637_CLK = 4;
const uint8_t PIN_TM1637_DIO = 5;

// -----------------------------
// Behavior configuration
// -----------------------------
const bool SENSOR_ACTIVE_LOW = true;
const unsigned long DEBOUNCE_MS = 20;
const unsigned long SEQUENCE_TIMEOUT_MS = 1200;
const unsigned long COOLDOWN_MS = 300;

// Display brightness: 0 to 7
const uint8_t DISPLAY_BRIGHTNESS = 4;

// -----------------------------
// Simple TM1637 driver
// -----------------------------
class SimpleTM1637 {
public:
  SimpleTM1637(uint8_t clkPin, uint8_t dioPin)
    : _clk(clkPin), _dio(dioPin) {}

  void begin() {
    pinMode(_clk, OUTPUT);
    pinMode(_dio, OUTPUT);
    digitalWrite(_clk, HIGH);
    digitalWrite(_dio, HIGH);
    setBrightness(DISPLAY_BRIGHTNESS, true);
  }

  void setBrightness(uint8_t brightness, bool on = true) {
    if (brightness > 7) brightness = 7;
    _brightnessCmd = 0x88 | (on ? brightness : 0);
  }

  void showNumber(int value) {
    if (value < 0) value = 0;
    if (value > 9999) value = 9999;

    uint8_t digits[4];
    digits[0] = encodeDigit((value / 1000) % 10);
    digits[1] = encodeDigit((value / 100) % 10);
    digits[2] = encodeDigit((value / 10) % 10);
    digits[3] = encodeDigit(value % 10);

    // Leading blanking except for zero itself
    if (value < 1000) digits[0] = 0x00;
    if (value < 100)  digits[1] = 0x00;
    if (value < 10)   digits[2] = 0x00;
    if (value == 0) {
      digits[0] = 0x00;
      digits[1] = 0x00;
      digits[2] = 0x00;
      digits[3] = encodeDigit(0);
    }

    setSegments(digits);
  }

  void showDashes() {
    uint8_t dash = 0x40;
    uint8_t digits[4] = {dash, dash, dash, dash};
    setSegments(digits);
  }

private:
  uint8_t _clk;
  uint8_t _dio;
  uint8_t _brightnessCmd = 0x8F;

  uint8_t encodeDigit(uint8_t digit) {
    static const uint8_t map[10] = {
      0x3F, // 0
      0x06, // 1
      0x5B, // 2
      0x4F, // 3
      0x66, // 4
      0x6D, // 5
      0x7D, // 6
      0x07, // 7
      0x7F, // 8
      0x6F  // 9
    };
    if (digit < 10) return map[digit];
    return 0x00;
  }

  void bitDelay() {
    delayMicroseconds(5);
  }

  void start() {
    pinMode(_dio, OUTPUT);
    digitalWrite(_dio, HIGH);
    digitalWrite(_clk, HIGH);
    bitDelay();
    digitalWrite(_dio, LOW);
  }

  void stop() {
    pinMode(_dio, OUTPUT);
    digitalWrite(_clk, LOW);
    bitDelay();
    digitalWrite(_dio, LOW);
    bitDelay();
    digitalWrite(_clk, HIGH);
    bitDelay();
    digitalWrite(_dio, HIGH);
  }

  bool writeByte(uint8_t b) {
    for (uint8_t i = 0; i < 8; i++) {
      digitalWrite(_clk, LOW);
      bitDelay();

      if (b & 0x01) {
        digitalWrite(_dio, HIGH);
      } else {
        digitalWrite(_dio, LOW);
      }

      bitDelay();
      digitalWrite(_clk, HIGH);
      bitDelay();
      b >>= 1;
    }

    // ACK
    digitalWrite(_clk, LOW);
    pinMode(_dio, INPUT_PULLUP);
    bitDelay();
    digitalWrite(_clk, HIGH);
    bitDelay();
    bool ack = (digitalRead(_dio) == LOW);
    digitalWrite(_clk, LOW);
    pinMode(_dio, OUTPUT);
    return ack;
  }

  void setSegments(uint8_t segments[4]) {
    start();
    writeByte(0x40); // auto increment mode
    stop();

    start();
    writeByte(0xC0); // address 0
    for (uint8_t i = 0; i < 4; i++) {
      writeByte(segments[i]);
    }
// ...

/*
  dual_ir_beam_visitor_counter.ino

  Target:
    Arduino UNO R3 (ATmega328P)

  Hardware:
    - 2x IR break-beam receivers on D2 and D3
    - TM1637 4-digit display on D4 (CLK) and D5 (DIO)

  Behavior:
    - Beam A then Beam B => ENTRY => count++
    - Beam B then Beam A => EXIT  => count-- (not below zero)
    - Serial output logs events for validation
    - Display shows occupancy count from 0 to 9999

  Notes:
    - Assumes receiver outputs are active-low:
        beam clear   -> HIGH
        beam broken  -> LOW
    - Uses INPUT_PULLUP for stable idle state
*/

#include <Arduino.h>

// -----------------------------
// Pin configuration
// -----------------------------
const uint8_t PIN_BEAM_A = 2;
const uint8_t PIN_BEAM_B = 3;
const uint8_t PIN_TM1637_CLK = 4;
const uint8_t PIN_TM1637_DIO = 5;

// -----------------------------
// Behavior configuration
// -----------------------------
const bool SENSOR_ACTIVE_LOW = true;
const unsigned long DEBOUNCE_MS = 20;
const unsigned long SEQUENCE_TIMEOUT_MS = 1200;
const unsigned long COOLDOWN_MS = 300;

// Display brightness: 0 to 7
const uint8_t DISPLAY_BRIGHTNESS = 4;

// -----------------------------
// Simple TM1637 driver
// -----------------------------
class SimpleTM1637 {
public:
  SimpleTM1637(uint8_t clkPin, uint8_t dioPin)
    : _clk(clkPin), _dio(dioPin) {}

  void begin() {
    pinMode(_clk, OUTPUT);
    pinMode(_dio, OUTPUT);
    digitalWrite(_clk, HIGH);
    digitalWrite(_dio, HIGH);
    setBrightness(DISPLAY_BRIGHTNESS, true);
  }

  void setBrightness(uint8_t brightness, bool on = true) {
    if (brightness > 7) brightness = 7;
    _brightnessCmd = 0x88 | (on ? brightness : 0);
  }

  void showNumber(int value) {
    if (value < 0) value = 0;
    if (value > 9999) value = 9999;

    uint8_t digits[4];
    digits[0] = encodeDigit((value / 1000) % 10);
    digits[1] = encodeDigit((value / 100) % 10);
    digits[2] = encodeDigit((value / 10) % 10);
    digits[3] = encodeDigit(value % 10);

    // Leading blanking except for zero itself
    if (value < 1000) digits[0] = 0x00;
    if (value < 100)  digits[1] = 0x00;
    if (value < 10)   digits[2] = 0x00;
    if (value == 0) {
      digits[0] = 0x00;
      digits[1] = 0x00;
      digits[2] = 0x00;
      digits[3] = encodeDigit(0);
    }

    setSegments(digits);
  }

  void showDashes() {
    uint8_t dash = 0x40;
    uint8_t digits[4] = {dash, dash, dash, dash};
    setSegments(digits);
  }

private:
  uint8_t _clk;
  uint8_t _dio;
  uint8_t _brightnessCmd = 0x8F;

  uint8_t encodeDigit(uint8_t digit) {
    static const uint8_t map[10] = {
      0x3F, // 0
      0x06, // 1
      0x5B, // 2
      0x4F, // 3
      0x66, // 4
      0x6D, // 5
      0x7D, // 6
      0x07, // 7
      0x7F, // 8
      0x6F  // 9
    };
    if (digit < 10) return map[digit];
    return 0x00;
  }

  void bitDelay() {
    delayMicroseconds(5);
  }

  void start() {
    pinMode(_dio, OUTPUT);
    digitalWrite(_dio, HIGH);
    digitalWrite(_clk, HIGH);
    bitDelay();
    digitalWrite(_dio, LOW);
  }

  void stop() {
    pinMode(_dio, OUTPUT);
    digitalWrite(_clk, LOW);
    bitDelay();
    digitalWrite(_dio, LOW);
    bitDelay();
    digitalWrite(_clk, HIGH);
    bitDelay();
    digitalWrite(_dio, HIGH);
  }

  bool writeByte(uint8_t b) {
    for (uint8_t i = 0; i < 8; i++) {
      digitalWrite(_clk, LOW);
      bitDelay();

      if (b & 0x01) {
        digitalWrite(_dio, HIGH);
      } else {
        digitalWrite(_dio, LOW);
      }

      bitDelay();
      digitalWrite(_clk, HIGH);
      bitDelay();
      b >>= 1;
    }

    // ACK
    digitalWrite(_clk, LOW);
    pinMode(_dio, INPUT_PULLUP);
    bitDelay();
    digitalWrite(_clk, HIGH);
    bitDelay();
    bool ack = (digitalRead(_dio) == LOW);
    digitalWrite(_clk, LOW);
    pinMode(_dio, OUTPUT);
    return ack;
  }

  void setSegments(uint8_t segments[4]) {
    start();
    writeByte(0x40); // auto increment mode
    stop();

    start();
    writeByte(0xC0); // address 0
    for (uint8_t i = 0; i < 4; i++) {
      writeByte(segments[i]);
    }
    stop();

    start();
    writeByte(_brightnessCmd);
    stop();
  }
};

SimpleTM1637 display(PIN_TM1637_CLK, PIN_TM1637_DIO);

// -----------------------------
// Debounced sensor input
// -----------------------------
struct DebouncedInput {
  uint8_t pin;
  bool stableState;
  bool lastRawState;
  unsigned long lastChangeMs;

  void begin(uint8_t p) {
    pin = p;
    pinMode(pin, INPUT_PULLUP);
    stableState = digitalRead(pin);
    lastRawState = stableState;
    lastChangeMs = millis();
  }

  void update() {
    bool raw = digitalRead(pin);
    unsigned long now = millis();

    if (raw != lastRawState) {
      lastRawState = raw;
      lastChangeMs = now;
    }

    if ((now - lastChangeMs) >= DEBOUNCE_MS) {
      stableState = lastRawState;
    }
  }

  bool isBroken() const {
    if (SENSOR_ACTIVE_LOW) {
      return stableState == LOW;
    } else {
      return stableState == HIGH;
    }
  }
};

DebouncedInput beamA;
DebouncedInput beamB;

// -----------------------------
// Direction state machine
// -----------------------------
enum CountState {
  IDLE,
  WAIT_B_AFTER_A,
  WAIT_A_AFTER_B,
  COOLDOWN
};

CountState countState = IDLE;
unsigned long stateStartMs = 0;
unsigned int occupancyCount = 0;

bool prevBrokenA = false;
bool prevBrokenB = false;

void enterState(CountState newState) {
  countState = newState;
  stateStartMs = millis();
}

void logState(const char* msg) {
  Serial.print("[");
  Serial.print(millis());
  Serial.print(" ms] ");
  Serial.println(msg);
}

void handleEvents() {
  bool brokenA = beamA.isBroken();
  bool brokenB = beamB.isBroken();

  bool risingBreakA = (!prevBrokenA && brokenA);
  bool risingBreakB = (!prevBrokenB && brokenB);

  unsigned long now = millis();

  switch (countState) {
    case IDLE:
      if (risingBreakA && !brokenB) {
        enterState(WAIT_B_AFTER_A);
        logState("Sequence start: A first");
      } else if (risingBreakB && !brokenA) {
        enterState(WAIT_A_AFTER_B);
        logState("Sequence start: B first");
      }
      break;

    case WAIT_B_AFTER_A:
      if ((now - stateStartMs) > SEQUENCE_TIMEOUT_MS) {
        enterState(IDLE);
        logState("TIMEOUT after A first");
      } else if (risingBreakB) {
        if (occupancyCount < 9999) {
          occupancyCount++;
        }
        display.showNumber(occupancyCount);
        Serial.print("[");
        Serial.print(now);
        Serial.print(" ms] ENTRY, count=");
        Serial.println(occupancyCount);
        enterState(COOLDOWN);
      }
      break;

    case WAIT_A_AFTER_B:
      if ((now - stateStartMs) > SEQUENCE_TIMEOUT_MS) {
        enterState(IDLE);
        logState("TIMEOUT after B first");
      } else if (risingBreakA) {
        if (occupancyCount > 0) {
          occupancyCount--;
        }
        display.showNumber(occupancyCount);
        Serial.print("[");
        Serial.print(now);
        Serial.print(" ms] EXIT, count=");
        Serial.println(occupancyCount);
        enterState(COOLDOWN);
      }
      break;

    case COOLDOWN:
      // Wait until both beams are clear or cooldown expires
      if ((!brokenA && !brokenB) && ((now - stateStartMs) > COOLDOWN_MS)) {
        enterState(IDLE);
      } else if ((now - stateStartMs) > (COOLDOWN_MS + 1000)) {
        enterState(IDLE);
      }
      break;
  }

  prevBrokenA = brokenA;
  prevBrokenB = brokenB;
}

void setup() {
  Serial.begin(115200);

  beamA.begin(PIN_BEAM_A);
  beamB.begin(PIN_BEAM_B);

  display.begin();
  display.showNumber(occupancyCount);

  prevBrokenA = beamA.isBroken();
  prevBrokenB = beamB.isBroken();

  logState("Dual IR beam visitor counter started");
  Serial.println("Rule: A then B = ENTRY, B then A = EXIT");
  Serial.println("Initial count=0");
}

void loop() {
  beamA.update();
  beamB.update();

  handleEvents();

  static unsigned long lastRefreshMs = 0;
  unsigned long now = millis();
  if ((now - lastRefreshMs) >= 100) {
    display.showNumber(occupancyCount);
    lastRefreshMs = now;
  }
}

Salida de referencia opcional del monitor serie

Esto no es código que subas, pero ayuda a saber cómo se ve un comportamiento normal:

[12 ms] Dual IR beam visitor counter started
Rule: A then B = ENTRY, B then A = EXIT
Initial count=0
[5021 ms] Sequence start: A first
[5288 ms] ENTRY, count=1
[9140 ms] Sequence start: B first
[9412 ms] EXIT, count=0
[12040 ms] Sequence start: A first
[13270 ms] TIMEOUT after A first

Comandos de compilación/carga/ejecución

Usa Arduino CLI exactamente como se requiere.

Tabla de comandos

Tarea Comando
Actualizar índice de placas arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno dual_ir_beam_visitor_counter
Cargar sketch arduino-cli upload --fqbn arduino:avr:uno --port <PORT> dual_ir_beam_visitor_counter
Abrir monitor serie arduino-cli monitor --port <PORT> --config 115200

Flujo de trabajo corto

  1. Crea la carpeta del sketch:

bash
mkdir -p dual_ir_beam_visitor_counter

  1. Guarda el archivo .ino como:

text
dual_ir_beam_visitor_counter/dual_ir_beam_visitor_counter.ino

  1. Ejecuta la tabla de comandos desde la carpeta que contiene el directorio del sketch.
  2. Sustituye <PORT> por el puerto real de tu dispositivo, por ejemplo:
  3. Linux: /dev/ttyACM0
  4. macOS: /dev/cu.usbmodem14101
  5. Windows: COM4
  6. Después de la carga, abre el monitor serie y pasa una mano u objeto a través de los haces.

Validación paso a paso

Usa estos puntos de control en orden. Están intencionalmente diseñados para ser prácticos y fáciles de reproducir en un banco de trabajo.

1. Verificación de alimentación y pantalla

Acción
– Conecta el UNO por USB.
– Carga el sketch.
– Observa la pantalla TM1637 inmediatamente después del reinicio.

Observación esperada
– La pantalla muestra 0.
– El monitor serie imprime texto de inicio.

Condición de aprobación
– La pantalla es estable y legible.
– El monitor serie muestra el banner de inicio sin reinicios aleatorios.

2. Verificación de alineación de un solo sensor

Acción
– Mantén el otro haz sin tocar.
– Interrumpe solo el haz A con tu dedo o una tarjeta.
– Luego libéralo.
– Repite solo con el haz B.

Observación esperada
– Puede que veas mensajes de inicio de secuencia en Serial cuando primero se interrumpe un haz.
– Si no completas el segundo haz, debería aparecer un mensaje de tiempo de espera después de aproximadamente 1.2 segundos.

Condición de aprobación
– Cada haz puede interrumpirse individualmente de manera repetible.
– No queda ningún estado permanentemente atascado después de liberar el haz.

3. Verificación de dirección de entrada

Acción
– Mueve un objeto a través del trayecto en la dirección A luego B.
– Hazlo lenta y claramente durante 5 pasadas, una pasada a la vez.

Observación esperada
– Para cada cruce limpio, Serial imprime:
Sequence start: A first
– luego ENTRY, count=<n>
– La pantalla incrementa en 1 cada vez.

Condición de aprobación
– El conteo mostrado aumenta exactamente una vez por cada cruce limpio A-luego-B.

4. Verificación de dirección de salida

Acción
– Ahora muévete a través del trayecto en la dirección B luego A.
– Realiza 5 pasadas limpias.

Observación esperada
– Para cada cruce limpio, Serial imprime:
Sequence start: B first
– luego EXIT, count=<n>
– La pantalla disminuye en 1 cada vez, pero no por debajo de cero.

Condición de aprobación
– La pantalla disminuye correctamente y nunca muestra un número negativo.

5. Verificación de tiempo de espera y activaciones falsas

Acción
– Interrumpe el haz A, mantenlo más tiempo que el tiempo de espera y luego retira la mano sin cruzar el haz B.
– Repite solo con el haz B.
– Prueba con toques accidentales rápidos cerca de un solo haz.

Observación esperada
– Serial imprime mensajes de tiempo de espera.
– El conteo de la pantalla no cambia.

Condición de aprobación
– Los cruces incompletos no cambian la ocupación.

6. Verificación de uso repetido

Acción
– Realiza 10 cruces mixtos en un orden conocido, por ejemplo:
– Entrada, Entrada, Salida, Entrada, Salida, Salida, Entrada, Entrada, Entrada, Salida
– Calcula manualmente el conteo final esperado.

Observación esperada
– La pantalla coincide con tu total manual.
– Los registros de Serial corresponden al orden de cruce.

Condición de aprobación
– La ocupación final mostrada es igual al valor esperado después de toda la secuencia de prueba.

Solución de problemas

Síntoma Causa probable Solución
La pantalla permanece en blanco TM1637 VCC/GND invertidos, pines CLK/DIO incorrectos, jumper defectuoso Revisa de nuevo la alimentación y confirma CLK -> D4, DIO -> D5
La pantalla enciende pero nunca cambia de 0 Salidas de sensores no conectadas, sensores desalineados, lógica de sensor incorrecta Confirma que las salidas de receptores van a D2 y D3; realinea emisores/receptores; verifica el comportamiento activo en bajo
El conteo aumenta al salir y disminuye al entrar Las etiquetas físicas de los haces A y B están invertidas Intercambia las posiciones físicas de los haces o intercambia las conexiones D2 y D3
Un cruce produce dos conteos Haces demasiado cercanos, reflejos, persona demorándose, enfriamiento insuficiente Aumenta la separación a 10-15 cm, estrecha el paso, reduce reflejos, mantén el paso de una persona a la vez
Conteos aleatorios sin que nadie cruce Interferencia IR ambiental, cables flojos, módulos de sensores inestables Aléjalo de la luz solar directa, ajusta conexiones, mejora el montaje
El monitor serie muestra tiempos de espera con frecuencia Haces demasiado separados o movimiento demasiado lento Reduce la separación entre haces o aumenta ligeramente SEQUENCE_TIMEOUT_MS
El conteo nunca disminuye por debajo de 0 pero las salidas parecen ignoradas El conteo inicial ya es cero Realiza primero algunas entradas y luego prueba salidas
La carga falla Puerto incorrecto o núcleo Arduino ausente Ejecuta de nuevo arduino-cli core update-index, arduino-cli core install arduino:avr y verifica el puerto correcto

Mejoras

Mejor fiabilidad física

  • Construye un marco de puerta más rígido con acrílico, madera o soportes impresos en 3D.
  • Añade protectores laterales alrededor de los receptores IR para reducir la interferencia de la luz ambiente.
  • Usa un paso más estrecho para que solo una persona pueda cruzar a la vez.

Mejor comportamiento del software

  • Guarda periódicamente el conteo en EEPROM para que sobreviva a ciclos de encendido.
  • Añade un botón de reinicio por pulsación larga para borrar el conteo cuando se sepa que la sala está vacía.
  • Ajusta:
  • DEBOUNCE_MS
  • SEQUENCE_TIMEOUT_MS
  • COOLDOWN_MS
    para adaptarlos a tu puerta y velocidad de paso.

Mejor usabilidad del sistema

  • Añade un zumbador o retroalimentación LED en cada evento válido de entrada/salida.
  • Añade un comando serie como reset o set 25 para calibración manual después de la instalación.
  • Añade un segundo modo de pantalla que alterne entre ocupación y total de entradas del día.

Lista de verificación final

  • [ ] Usé exactamente Arduino UNO R3 (ATmega328P) + dos sensores IR de barrera óptica + pantalla TM1637 de 4 dígitos.
  • [ ] La salida del receptor del haz A está conectada a D2.
  • [ ] La salida del receptor del haz B está conectada a D3.
  • [ ] TM1637 CLK -> D4 y DIO -> D5.
  • [ ] Todos los módulos comparten correctamente 5V y GND.
  • [ ] Guardé el sketch como dual_ir_beam_visitor_counter/dual_ir_beam_visitor_counter.ino.
  • [ ] Ejecuté:
  • [ ] arduino-cli core update-index
  • [ ] arduino-cli core install arduino:avr
  • [ ] arduino-cli compile --fqbn arduino:avr:uno dual_ir_beam_visitor_counter
  • [ ] arduino-cli upload --fqbn arduino:avr:uno --port <PORT> dual_ir_beam_visitor_counter
  • [ ] La pantalla muestra 0 al inicio.
  • [ ] A luego B incrementa el conteo mostrado.
  • [ ] B luego A decrementa el conteo mostrado.
  • [ ] Los cruces incompletos producen registros de tiempo de espera pero no cambian el conteo.
  • [ ] El prototipo está montado en un trayecto de puerta estrecho y realista para uso práctico.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué microcontrolador se utiliza en el sistema descrito?




Pregunta 2: ¿Cuántos sensores IR de barrera óptica usa el contador de visitantes?




Pregunta 3: ¿Qué tipo de pantalla muestra el conteo de ocupación?




Pregunta 4: ¿Cómo determina el sistema una entrada?




Pregunta 5: ¿Cómo determina el sistema una salida?




Pregunta 6: ¿Qué muestra el sistema en tiempo real?




Pregunta 7: ¿Cuál es la respuesta típica por evento del sistema?




Pregunta 8: ¿En cuál de estos casos de uso se menciona explícitamente el sistema?




Pregunta 9: ¿Qué ventaja se destaca para supervisar áreas restringidas?




Pregunta 10: ¿Dónde se recomienda montar los haces para estimar la ocupación de una sala?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: temporizador con relé en Arduino UNO

Caso práctico: temporizador con relé en Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un temporizador de cuenta regresiva con relé autónomo para taller usando un Arduino UNO R3, pulsadores, un módulo de relé de 1 canal y un zumbador piezoeléctrico. Los usuarios pueden establecer una duración, iniciar una cuenta regresiva no bloqueante con millis(), mantener energizada una carga de 5 V o 12 V a través del relé mientras quede tiempo, y activar una alerta audible cuando el temporizador llegue a 0.

Por qué importa / Casos de uso

  • Apagar automáticamente pequeñas cargas de banco como un ventilador de 5 V, una bomba en miniatura o una lámpara de prueba de 12 V después de un ciclo de trabajo fijo de 30 s a 10 min.
  • Mejorar la repetibilidad en tareas de taller como calentamiento de adhesivos, extracción de humos de PCB, ciclos de limpieza o activación de útiles donde una temporización consistente importa más que la estimación manual.
  • Enseñar habilidades fundamentales de sistemas embebidos en un solo montaje: lectura de botones, temporización de antirrebote en el rango de 10–50 ms, control de relé, retroalimentación con zumbador y lógica de máquina de estados finitos sin usar delay().
  • Funcionar totalmente de forma autónoma después de la carga, con respuesta de botones casi instantánea bajo tiempos de bucle típicos de unos pocos milisegundos y sin necesidad de conexión al PC.

Resultado esperado

  • Un temporizador que permite al usuario aumentar o disminuir la duración con pulsadores y luego iniciar o detener la cuenta regresiva de forma fiable.
  • Una salida de relé que permanece activa durante el intervalo programado y se apaga inmediatamente cuando expira el tiempo o se cancela el ciclo.
  • Un patrón de zumbador al finalizar, por ejemplo 2–3 pitidos cortos durante 1–2 s, para señalar claramente el final de la ejecución temporizada.
  • Una implementación de Arduino con baja carga y buena capacidad de respuesta: sin uso significativo de GPU, demanda muy baja de CPU en el ATmega328P y latencia de control inferior a 10 ms para eventos normales de botones y salidas.

Audiencia: principiantes de Arduino, makers, estudiantes y usuarios de talleres de electrónica; Nivel: principiante a intermedio-bajo

Arquitectura/flujo: Los pulsadores alimentan las entradas digitales del UNO; el sketch elimina el rebote de las pulsaciones, actualiza el tiempo seleccionado y rastrea el estado de la cuenta regresiva con millis(); el UNO acciona la entrada del relé para energizar la carga externa durante la ventana activa y conmuta el zumbador piezoeléctrico cuando la cuenta regresiva se completa.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 3 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este prototipo está pensado para educación y experimentación a nivel de banco. Sus límites son importantes:

  • No uses este montaje para principiantes para conmutar tensión de red doméstica a menos que estés capacitado y tu laboratorio lo permita explícitamente.
  • Los módulos de relé pueden exponer tensiones peligrosas en sus terminales de contacto.
  • Para validación en aula, usa solo una carga de CC de baja tensión.
  • El lado Arduino y el lado de contactos del relé son dominios eléctricos distintos. Trata con cuidado el cableado de contactos del relé aunque el lado de control sea solo de 5 V.
  • No uses este temporizador para controlar:
  • herramientas críticas para la seguridad
  • sistemas de calefacción desatendidos
  • dispositivos médicos
  • sistemas de vehículos
  • maquinaria de alta potencia
  • Si más adelante conectas motores, solenoides u otras cargas inductivas, pueden requerirse protección adicional y un diseño de alimentación adecuado.
  • Mantén el cableado aislado, estable y con alivio de tensión. Un cableado de relé flojo puede crear conmutación poco fiable o cortocircuitos peligrosos.
  • El zumbador piezoeléctrico es solo una ayuda de alerta. No se garantiza que se oiga en un taller ruidoso.
  • Este tutorial muestra un prototipo educativo útil, no un producto certificado.

Prerrequisitos

Antes de empezar, asegúrate de tener:

  • Familiaridad básica con el concepto del IDE de Arduino, aunque este tutorial usa Arduino CLI
  • Un cable USB para el Arduino UNO R3
  • Un ordenador con:
  • Arduino CLI instalado
  • Un terminal serie disponible, o el Monitor Serie del IDE de Arduino si lo prefieres
  • Comprensión básica de:
  • Entradas digitales con resistencias pull-up
  • Salidas digitales
  • Uso seguro de módulos de relé
  • Una carga de baja tensión para probar la salida del relé
  • Recomendado: una pequeña lámpara de CC, módulo de carga LED, ventilador de baja tensión o línea de alimentación de un pequeño motor de CC a través de los contactos del relé
  • No empieces con cargas de red doméstica en un laboratorio para principiantes

Materiales

Usa exactamente la plataforma indicada a continuación.

  • Arduino UNO R3 (ATmega328P)
  • 3 pulsadores
    Etiquetas sugeridas:
  • ADD
  • START/PAUSE
  • CANCEL/RESET
  • Módulo de relé de 1 canal
  • Versión de bobina/módulo de 5 V compatible con lógica Arduino
  • Se prefiere un módulo con controlador por transistor y LED indicador de entrada
  • Zumbador piezoeléctrico
  • Zumbador pasivo o activo; este tutorial asume un zumbador piezoeléctrico simple que puede accionarse desde un pin digital
  • Protoboard
  • Cables jumper
  • Cable USB para Arduino UNO R3
  • Opcional pero recomendado:
  • Pequeña lámpara o ventilador de CC de baja tensión como carga de prueba conmutada por el relé
  • Fuente externa de baja tensión para esa carga de prueba si la carga lo requiere

Configuración/Conexión

Este proyecto usa las resistencias pull-up internas del Arduino para todos los pulsadores. Eso significa que cada botón se conecta entre el pin de entrada y GND. Cuando no se pulsa, la entrada lee HIGH. Cuando se pulsa, lee LOW.

Asignación de pines

Función Pin Arduino Detalles de cableado
Botón ADD D2 Un lado del pulsador a D2, el otro lado a GND
Botón START/PAUSE D3 Un lado del pulsador a D3, el otro lado a GND
Botón CANCEL/RESET D4 Un lado del pulsador a D4, el otro lado a GND
Entrada de relé D8 IN del módulo de relé a D8
Zumbador D9 Señal del zumbador a D9, tierra del zumbador a GND
VCC del relé 5V VCC del módulo de relé a 5V del Arduino
GND del relé GND GND del módulo de relé a GND del Arduino
GND del zumbador GND Tierra del zumbador a GND del Arduino
Alimentación/datos USB USB Arduino al ordenador

Notas de conexión

  1. Pulsadores
  2. Conecta un terminal de cada botón al pin digital asignado.
  3. Conecta el otro terminal de cada botón a GND.
  4. No se necesitan resistencias externas porque el sketch habilita INPUT_PULLUP.

  5. Módulo de relé

  6. Conecta:
    • VCC -> 5V
    • GND -> GND
    • IN -> D8
  7. Muchos módulos de relé son activos en bajo, lo que significa que el relé se enciende cuando el pin de Arduino entrega LOW.
  8. El código siguiente está escrito para un módulo de relé activo en bajo común e incluye una constante que puedes cambiar si tu módulo es activo en alto.

  9. Zumbador piezoeléctrico

  10. Conecta el pin positivo/señal a D9.
  11. Conecta el pin negativo a GND.
  12. Para muchos zumbadores piezoeléctricos pequeños, la conexión directa es aceptable para un prototipo educativo básico.

  13. Lado de contactos del relé

  14. Los terminales de contacto del relé están separados del lado Arduino.
  15. Para una salida controlada por temporizador, usa los terminales COM y NO del relé si quieres que la carga se encienda solo mientras la temporización esté activa.
  16. Mantén tu primera prueba simple:
    • Usa una carga de baja tensión
    • Usa una fuente conocida y segura
    • Verifica que el relé conmuta correctamente antes de conectar algo más complejo

Comportamiento operativo sugerido

  • Botón ADD: aumenta el tiempo preestablecido en 1 minuto, de 1 a 60 minutos, luego vuelve a 1
  • Botón START/PAUSE: inicia el temporizador si está inactivo; pausa/reanuda si ya está en ejecución
  • Botón CANCEL/RESET: detiene el temporizador y vuelve al valor preestablecido actual
  • Retroalimentación del zumbador:
  • Pitido corto en acciones válidas de botón
  • Patrón distintivo de múltiples pitidos cuando se completa el tiempo

Código validado

workshop_countdown_relay_timer.ino

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

/*
  workshop_countdown_relay_timer.ino

  Device model:
  Arduino UNO R3 (ATmega328P) + pushbuttons + 1-channel relay module + piezo buzzer

  Project goal:
  workshop-countdown-relay-timer

  Behavior:
  - ADD button: increase preset time by 1 minute (1..60, wrap around)
  - START/PAUSE button:
      * if idle -> start countdown
      * if running -> pause
      * if paused -> resume
  - CANCEL/RESET button:
      * stop countdown
      * relay off
      * restore remaining time to preset
  - Relay is active while countdown is running
  - Buzzer gives short feedback on actions and a completion pattern at the end

  Notes:
  - Buttons use INPUT_PULLUP, so pressed = LOW
  - Relay modules are often active-low; set RELAY_ACTIVE_LOW accordingly
*/

const byte PIN_BTN_ADD = 2;
const byte PIN_BTN_START = 3;
const byte PIN_BTN_CANCEL = 4;
const byte PIN_RELAY = 8;
const byte PIN_BUZZER = 9;

const bool RELAY_ACTIVE_LOW = true;

const unsigned long DEBOUNCE_MS = 35;
const unsigned long STATUS_PRINT_MS = 1000;
const unsigned long MINUTES_MAX = 60;
const unsigned long DEFAULT_PRESET_MIN = 5;

enum TimerState {
  IDLE,
  RUNNING,
  PAUSED,
  FINISHED
};

TimerState timerState = IDLE;

unsigned long presetMinutes = DEFAULT_PRESET_MIN;
unsigned long remainingMs = DEFAULT_PRESET_MIN * 60UL * 1000UL;
unsigned long lastTickMs = 0;
unsigned long lastStatusPrintMs = 0;

struct Button {
  byte pin;
  bool stableState;
  bool lastReading;
  unsigned long lastChangeMs;
};

Button btnAdd    = {PIN_BTN_ADD, HIGH, HIGH, 0};
Button btnStart  = {PIN_BTN_START, HIGH, HIGH, 0};
Button btnCancel = {PIN_BTN_CANCEL, HIGH, HIGH, 0};

void setRelay(bool on) {
  if (RELAY_ACTIVE_LOW) {
    digitalWrite(PIN_RELAY, on ? LOW : HIGH);
  } else {
    digitalWrite(PIN_RELAY, on ? HIGH : LOW);
  }
}

void beep(unsigned int frequency, unsigned long durationMs) {
  tone(PIN_BUZZER, frequency, durationMs);
}

void shortBeep() {
  beep(2200, 70);
}

void doubleBeep() {
  beep(2000, 60);
  delay(100);
  beep(2600, 80);
}

void finishBeepPattern() {
  for (int i = 0; i < 3; i++) {
    beep(1800, 120);
    delay(180);
  }
  beep(2600, 350);
  delay(400);
}

bool buttonPressedEvent(Button &button) {
  bool reading = digitalRead(button.pin);

  if (reading != button.lastReading) {
    button.lastChangeMs = millis();
    button.lastReading = reading;
  }

  if ((millis() - button.lastChangeMs) > DEBOUNCE_MS) {
    if (reading != button.stableState) {
      button.stableState = reading;

      // Because INPUT_PULLUP is used, LOW means pressed
      if (button.stableState == LOW) {
        return true;
      }
    }
  }

  return false;
}

void printHelp() {
  Serial.println(F("Workshop Countdown Relay Timer"));
  Serial.println(F("Buttons:"));
  Serial.println(F("  ADD         -> +1 minute preset (1..60)"));
  Serial.println(F("  START/PAUSE -> start, pause, resume"));
  Serial.println(F("  CANCEL      -> stop and reset to preset"));
  Serial.println();
}

void printStateLine() {
  unsigned long totalSeconds = remainingMs / 1000UL;
  unsigned int minutesPart = totalSeconds / 60UL;
  unsigned int secondsPart = totalSeconds % 60UL;

  Serial.print(F("Preset="));
  Serial.print(presetMinutes);
  Serial.print(F(" min, Remaining="));
  if (minutesPart < 10) Serial.print('0');
  Serial.print(minutesPart);
  Serial.print(':');
  if (secondsPart < 10) Serial.print('0');
  Serial.print(secondsPart);
  Serial.print(F(", State="));

  switch (timerState) {
    case IDLE: Serial.print(F("IDLE")); break;
    case RUNNING: Serial.print(F("RUNNING")); break;
    case PAUSED: Serial.print(F("PAUSED")); break;
    case FINISHED: Serial.print(F("FINISHED")); break;
  }
// ...

/*
  workshop_countdown_relay_timer.ino

  Device model:
  Arduino UNO R3 (ATmega328P) + pushbuttons + 1-channel relay module + piezo buzzer

  Project goal:
  workshop-countdown-relay-timer

  Behavior:
  - ADD button: increase preset time by 1 minute (1..60, wrap around)
  - START/PAUSE button:
      * if idle -> start countdown
      * if running -> pause
      * if paused -> resume
  - CANCEL/RESET button:
      * stop countdown
      * relay off
      * restore remaining time to preset
  - Relay is active while countdown is running
  - Buzzer gives short feedback on actions and a completion pattern at the end

  Notes:
  - Buttons use INPUT_PULLUP, so pressed = LOW
  - Relay modules are often active-low; set RELAY_ACTIVE_LOW accordingly
*/

const byte PIN_BTN_ADD = 2;
const byte PIN_BTN_START = 3;
const byte PIN_BTN_CANCEL = 4;
const byte PIN_RELAY = 8;
const byte PIN_BUZZER = 9;

const bool RELAY_ACTIVE_LOW = true;

const unsigned long DEBOUNCE_MS = 35;
const unsigned long STATUS_PRINT_MS = 1000;
const unsigned long MINUTES_MAX = 60;
const unsigned long DEFAULT_PRESET_MIN = 5;

enum TimerState {
  IDLE,
  RUNNING,
  PAUSED,
  FINISHED
};

TimerState timerState = IDLE;

unsigned long presetMinutes = DEFAULT_PRESET_MIN;
unsigned long remainingMs = DEFAULT_PRESET_MIN * 60UL * 1000UL;
unsigned long lastTickMs = 0;
unsigned long lastStatusPrintMs = 0;

struct Button {
  byte pin;
  bool stableState;
  bool lastReading;
  unsigned long lastChangeMs;
};

Button btnAdd    = {PIN_BTN_ADD, HIGH, HIGH, 0};
Button btnStart  = {PIN_BTN_START, HIGH, HIGH, 0};
Button btnCancel = {PIN_BTN_CANCEL, HIGH, HIGH, 0};

void setRelay(bool on) {
  if (RELAY_ACTIVE_LOW) {
    digitalWrite(PIN_RELAY, on ? LOW : HIGH);
  } else {
    digitalWrite(PIN_RELAY, on ? HIGH : LOW);
  }
}

void beep(unsigned int frequency, unsigned long durationMs) {
  tone(PIN_BUZZER, frequency, durationMs);
}

void shortBeep() {
  beep(2200, 70);
}

void doubleBeep() {
  beep(2000, 60);
  delay(100);
  beep(2600, 80);
}

void finishBeepPattern() {
  for (int i = 0; i < 3; i++) {
    beep(1800, 120);
    delay(180);
  }
  beep(2600, 350);
  delay(400);
}

bool buttonPressedEvent(Button &button) {
  bool reading = digitalRead(button.pin);

  if (reading != button.lastReading) {
    button.lastChangeMs = millis();
    button.lastReading = reading;
  }

  if ((millis() - button.lastChangeMs) > DEBOUNCE_MS) {
    if (reading != button.stableState) {
      button.stableState = reading;

      // Because INPUT_PULLUP is used, LOW means pressed
      if (button.stableState == LOW) {
        return true;
      }
    }
  }

  return false;
}

void printHelp() {
  Serial.println(F("Workshop Countdown Relay Timer"));
  Serial.println(F("Buttons:"));
  Serial.println(F("  ADD         -> +1 minute preset (1..60)"));
  Serial.println(F("  START/PAUSE -> start, pause, resume"));
  Serial.println(F("  CANCEL      -> stop and reset to preset"));
  Serial.println();
}

void printStateLine() {
  unsigned long totalSeconds = remainingMs / 1000UL;
  unsigned int minutesPart = totalSeconds / 60UL;
  unsigned int secondsPart = totalSeconds % 60UL;

  Serial.print(F("Preset="));
  Serial.print(presetMinutes);
  Serial.print(F(" min, Remaining="));
  if (minutesPart < 10) Serial.print('0');
  Serial.print(minutesPart);
  Serial.print(':');
  if (secondsPart < 10) Serial.print('0');
  Serial.print(secondsPart);
  Serial.print(F(", State="));

  switch (timerState) {
    case IDLE: Serial.print(F("IDLE")); break;
    case RUNNING: Serial.print(F("RUNNING")); break;
    case PAUSED: Serial.print(F("PAUSED")); break;
    case FINISHED: Serial.print(F("FINISHED")); break;
  }

  Serial.print(F(", Relay="));
  Serial.println((timerState == RUNNING) ? F("ON") : F("OFF"));
}

void resetToPreset() {
  remainingMs = presetMinutes * 60UL * 1000UL;
  lastTickMs = millis();
}

void handleAddButton() {
  // For simplicity in a basic project, allow preset changes only when not running
  if (timerState == RUNNING) {
    doubleBeep();
    Serial.println(F("ADD ignored while running. Pause or cancel first."));
    return;
  }

  presetMinutes++;
  if (presetMinutes > MINUTES_MAX) {
    presetMinutes = 1;
  }

  resetToPreset();

  if (timerState == FINISHED) {
    timerState = IDLE;
  }

  shortBeep();
  Serial.print(F("Preset changed to "));
  Serial.print(presetMinutes);
  Serial.println(F(" minute(s)."));
  printStateLine();
}

void handleStartButton() {
  if (timerState == IDLE || timerState == FINISHED) {
    resetToPreset();
    timerState = RUNNING;
    setRelay(true);
    lastTickMs = millis();
    shortBeep();
    Serial.println(F("Countdown started."));
    printStateLine();
    return;
  }

  if (timerState == RUNNING) {
    timerState = PAUSED;
    setRelay(false);
    shortBeep();
    Serial.println(F("Countdown paused."));
    printStateLine();
    return;
  }

  if (timerState == PAUSED) {
    timerState = RUNNING;
    setRelay(true);
    lastTickMs = millis();
    shortBeep();
    Serial.println(F("Countdown resumed."));
    printStateLine();
    return;
  }
}

void handleCancelButton() {
  timerState = IDLE;
  setRelay(false);
  resetToPreset();
  doubleBeep();
  Serial.println(F("Countdown canceled and reset to preset."));
  printStateLine();
}

void updateTimer() {
  if (timerState != RUNNING) {
    return;
  }

  unsigned long now = millis();
  unsigned long elapsed = now - lastTickMs;
  lastTickMs = now;

  if (elapsed >= remainingMs) {
    remainingMs = 0;
    timerState = FINISHED;
    setRelay(false);
    Serial.println(F("Countdown complete. Relay turned OFF."));
    printStateLine();
    finishBeepPattern();
    return;
  }

  remainingMs -= elapsed;
}

void setup() {
  pinMode(PIN_BTN_ADD, INPUT_PULLUP);
  pinMode(PIN_BTN_START, INPUT_PULLUP);
  pinMode(PIN_BTN_CANCEL, INPUT_PULLUP);

  pinMode(PIN_RELAY, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);

  setRelay(false);

  Serial.begin(9600);
  delay(300);

  printHelp();
  resetToPreset();
  printStateLine();
}

void loop() {
  if (buttonPressedEvent(btnAdd)) {
    handleAddButton();
  }

  if (buttonPressedEvent(btnStart)) {
    handleStartButton();
  }

  if (buttonPressedEvent(btnCancel)) {
    handleCancelButton();
  }

  updateTimer();

  unsigned long now = millis();
  if (now - lastStatusPrintMs >= STATUS_PRINT_MS) {
    lastStatusPrintMs = now;
    printStateLine();
  }
}

Qué está haciendo el código

Este sketch usa una máquina de estados con cuatro estados:

  • IDLE – esperando entrada del usuario
  • RUNNING – cuenta regresiva activa, relé encendido
  • PAUSED – cuenta regresiva congelada, relé apagado
  • FINISHED – la cuenta regresiva llegó a cero, relé apagado, alerta del zumbador completada

Decisiones de diseño importantes y amigables para principiantes:

  • Sin bucle de cuenta regresiva bloqueante
    El temporizador usa millis() en lugar de un delay() largo, por lo que las pulsaciones de botones siguen siendo responsivas.
  • Botones con antirrebote
    Cada botón se filtra con un pequeño intervalo de antirrebote para reducir activaciones falsas por rebote de contacto.
  • El tiempo preestablecido y el tiempo restante están separados
    presetMinutes almacena la duración elegida, mientras que remainingMs rastrea lo que queda durante una ejecución.
  • Salida de estado por serie
    El Arduino imprime actualizaciones periódicas, lo que facilita mucho la resolución de problemas durante la validación.

Comandos de compilación/carga/ejecución

Tabla de comandos

Propósito Comando
Actualizar índice de placas arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno workshop_countdown_relay_timer
Cargar sketch arduino-cli upload --fqbn arduino:avr:uno --port <PORT> workshop_countdown_relay_timer

Ejemplo de sesión de terminal

arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli compile --fqbn arduino:avr:uno workshop_countdown_relay_timer
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> workshop_countdown_relay_timer

Flujo de trabajo corto

  1. Crea una carpeta llamada workshop_countdown_relay_timer.
  2. Guarda el sketch como:
  3. workshop_countdown_relay_timer/workshop_countdown_relay_timer.ino
  4. Abre una terminal en el directorio padre de esa carpeta.
  5. Ejecuta el comando de compilación.
  6. Conecta el Arduino UNO R3 por USB.
  7. Sustituye <PORT> por tu puerto serie real y ejecuta el comando de carga.
  8. Ejemplo Linux: /dev/ttyACM0
  9. Ejemplo Windows: COM4
  10. Ejemplo macOS: /dev/cu.usbmodem14101
  11. Abre un monitor serie a 9600 baud para observar el estado del temporizador.

Validación paso a paso

Usa estos puntos de control en orden. El objetivo es validar el comportamiento exacto del temporizador de cuenta regresiva con relé para taller.

1. Encendido y estado inactivo

Acción
– Conecta el Arduino por USB.
– Abre el monitor serie a 9600 baud.

Observación esperada
– Deberías ver un banner de inicio como:
Workshop Countdown Relay Timer
– También deberías ver una línea de estado con:
Preset=5 min
State=IDLE
Relay=OFF

Condición de aprobación
– El relé no está energizado al arrancar.
– El monitor serie muestra el tiempo preestablecido y el estado inactivo sin caracteres aleatorios.

2. Ajuste del preajuste usando el botón ADD

Acción
– Pulsa el botón ADD una vez.
– Luego púlsalo varias veces más.

Observación esperada
– Cada pulsación aumenta el preajuste en 1 minuto.
– El monitor serie informa:
Preset changed to 6 minute(s).
– y líneas de estado actualizadas
– Después de 60 minutos, la siguiente pulsación vuelve a 1 minuto.

Condición de aprobación
– Una pulsación de botón produce un cambio de preajuste.
– No se produce conteo doble frecuente por rebote del interruptor.
– El temporizador permanece en IDLE y el relé permanece apagado durante el ajuste del preajuste.

3. Iniciar la cuenta regresiva y confirmar la activación del relé

Acción
– Establece un preajuste de prueba corto como 1 minuto.
– Pulsa START/PAUSE una vez.

Observación esperada
– La salida serie dice:
Countdown started.
State=RUNNING
Relay=ON
– El LED indicador del módulo de relé, si está presente, debería cambiar de estado.
– Tu carga de prueba de baja tensión conectada debería encenderse a través del relé.

Condición de aprobación
– El relé se activa solo cuando el temporizador entra en RUNNING.
– El tiempo restante disminuye una vez por segundo en el monitor serie.

4. Comportamiento de pausa, reanudación y cancelación

Acción
– Mientras está en ejecución, pulsa START/PAUSE otra vez.
– Púlsalo una vez más para reanudar.
– Luego pulsa CANCEL/RESET.

Observación esperada
– Al pausar:
State=PAUSED
Relay=OFF
– Al reanudar:
State=RUNNING
Relay=ON
– Al cancelar:
Countdown canceled and reset to preset.
State=IDLE
Relay=OFF

Condición de aprobación
– La pausa detiene la cuenta regresiva sin perder el tiempo restante.
– La reanudación continúa desde el tiempo restante pausado.
– La cancelación apaga inmediatamente el relé y restaura el valor preestablecido completo.

5. Prueba de finalización de la cuenta regresiva

Acción
– Establece el preajuste en 1 minuto y deja que llegue a cero sin pulsar botones.

Observación esperada
– Al completarse:
– La salida serie informa Countdown complete. Relay turned OFF.
– El estado cambia a FINISHED
– El zumbador emite un patrón de finalización reconocible
– La carga de prueba de baja tensión se apaga en el mismo momento en que el relé se desactiva.

Condición de aprobación
– El relé se apaga automáticamente al agotarse el tiempo.
– El zumbador de finalización suena una vez por cada evento de finalización de cuenta regresiva.
– El sistema no se reinicia por sí solo.

Resolución de problemas

Síntoma Causa probable Solución
La pulsación del botón no hace nada Botón cableado a 5V en lugar de GND, pin incorrecto, mal contacto en la protoboard Verifica que cada botón vaya entre el pin y GND, y coincida con D2/D3/D4
El relé está siempre encendido El comportamiento activo en bajo del módulo de relé no coincide, o error de cableado Revisa RELAY_ACTIVE_LOW; si es necesario cámbialo a false y vuelve a cargar
El relé hace clic pero la carga no conmuta Contactos COM/NO/NC cableados incorrectamente Usa correctamente los terminales de contacto del relé; para comportamiento normal encendido-temporizado usa COM y NO
Zumbador silencioso Polaridad del zumbador invertida, pin incorrecto o tipo de zumbador incompatible Confirma que el zumbador esté en D9 y GND; prueba otro zumbador piezoeléctrico
El monitor serie muestra texto ilegible Tasa de baudios incorrecta Ajusta el monitor serie a 9600 baud
El temporizador se reinicia inesperadamente Alimentación USB inestable o ruido de la carga afectando la fuente Usa un cable USB estable, mantén ordenado el cableado del relé/carga y evita alimentar cargas ruidosas desde la línea de 5V del Arduino
Una pulsación provoca dos acciones Rebote del interruptor o conexión floja Vuelve a asentar los cables, usa mejores pulsadores y mantén corto el cableado de botones
La carga falla Puerto incorrecto, falta el núcleo de la placa, problema de cable Vuelve a ejecutar la instalación del núcleo, verifica el puerto, usa un cable USB con capacidad de datos conocido

Mejoras

Mejoras de usabilidad

  • Añade un display de 7 segmentos de 4 dígitos o LCD para mostrar el tiempo restante sin usar el monitor serie.
  • Añade una función de pulsación larga en el botón ADD para ajustar el tiempo más rápido.
  • Guarda el último preajuste en EEPROM para que el temporizador lo recuerde tras una pérdida de alimentación.

Funciones de control y temporización

  • Añade un modo de ajuste de 10 segundos para temporización de procesos cortos.
  • Añade una opción de silencio del zumbador después de la ejecución para que el temporizador pueda usarse en aulas más silenciosas.
  • Añade un modo de sobretiempo del relé, donde un ventilador sigue funcionando un minuto extra después de que termina un ciclo de trabajo.

Robustez eléctrica

  • Usa una carcasa de proyecto adecuada con botones etiquetados.
  • Añade terminales de tornillo para el lado de la carga y alivio de tensión para los cables.
  • Si conmutas cargas inductivas de CC, añade los métodos de supresión adecuados en el lado de la carga según el módulo y el tipo de carga.

Lista de verificación final

  • [ ] Usé Arduino UNO R3 (ATmega328P)
  • [ ] Usé 3 pulsadores, módulo de relé de 1 canal y zumbador piezoeléctrico
  • [ ] Los botones están cableados desde el pin de entrada a GND
  • [ ] El módulo de relé está conectado a 5V, GND y D8
  • [ ] El zumbador está conectado a D9 y GND
  • [ ] Guardé el archivo como workshop_countdown_relay_timer/workshop_countdown_relay_timer.ino
  • [ ] Ejecuté arduino-cli core update-index
  • [ ] Ejecuté arduino-cli core install arduino:avr
  • [ ] Compilé con arduino-cli compile --fqbn arduino:avr:uno workshop_countdown_relay_timer
  • [ ] Cargué con arduino-cli upload --fqbn arduino:avr:uno --port <PORT> workshop_countdown_relay_timer
  • [ ] El monitor serie a 9600 baud muestra el estado de inicio
  • [ ] ADD cambia el tiempo preestablecido
  • [ ] START inicia la cuenta regresiva y enciende el relé
  • [ ] PAUSE apaga el relé y conserva el tiempo restante
  • [ ] CANCEL detiene el temporizador y restaura el preajuste
  • [ ] La finalización de la cuenta regresiva apaga el relé y activa el patrón del zumbador

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué dispositivo principal se usa como controlador en el temporizador descrito?




Pregunta 2: ¿Qué técnica se utiliza para realizar la cuenta regresiva sin bloqueo?




Pregunta 3: ¿Qué componente mantiene energizada una carga de 5 V o 12 V mientras quede tiempo?




Pregunta 4: ¿Qué ocurre cuando el temporizador llega a 0?




Pregunta 5: ¿Cuál de las siguientes cargas se menciona como ejemplo de uso?




Pregunta 6: ¿Qué rango de tiempo de antirrebote para botones se menciona en el artículo?




Pregunta 7: ¿Qué habilidad de sistemas embebidos se enseña explícitamente evitando usar delay()?




Pregunta 8: ¿Para qué sirve el zumbador piezoeléctrico en este montaje?




Pregunta 9: ¿Qué rango de duración puede establecer el usuario para el ciclo temporizado?




Pregunta 10: ¿Qué ventaja operativa se destaca tras cargar el programa en el Arduino?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: monitor solar de batería con Arduino UNO

Caso práctico: monitor solar de batería con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un monitor solar de batería basado en Arduino que lee el voltaje de una batería de 12 V mediante un divisor de voltaje, mide la corriente de carga o de consumo con un sensor ACS712 y muestra ambos valores en una pantalla LCD HD44780 de 16×2. El sistema ofrece lecturas locales casi en tiempo real con una actualización simple de alrededor de 2–5 veces/seg y una latencia de visualización típica inferior a 500 ms.

Por qué importa / Casos de uso

  • Confirmar que un panel solar pequeño realmente está cargando al comprobar una corriente positiva, por ejemplo de +0.3 A a +1.8 A bajo sol directo en un cobertizo, una caja de camping o una configuración de demostración en aula.
  • Reducir daños a la batería detectando voltaje bajo con anticipación, por ejemplo cuando una batería de plomo-ácido de 12 V cae hacia 11.8–12.0 V mientras alimenta luces LED, un respaldo para router o un pequeño ventilador DC.
  • Medir la corriente real de consumo colocando el ACS712 en serie con una lámpara, bomba o convertidor USB y verificando el consumo esperado, como 0.15 A para LEDs o 0.8 A para una bomba pequeña.
  • Usarlo como un monitor de banco reutilizable junto a un controlador de carga o caja de batería para comprobaciones rápidas de mantenimiento sin necesidad de una laptop, app o panel en la nube.
  • Aprender medición embebida práctica, incluyendo sensado analógico, calibración, escalado ADC y manejo de ruido del sensor en un proyecto Arduino de bajo consumo.

Resultado esperado

  • Una lectura funcional en LCD que muestre continuamente el voltaje y la corriente de la batería, por ejemplo “12.6 V” y “+0.42 A”.
  • Lecturas de voltaje escaladas desde el divisor y lecturas de corriente interpretadas desde el ACS712 con calibración básica para valores más estables.
  • Un monitor compacto adecuado para pequeños experimentos solares de 12 V, sin uso significativo de GPU ni dependencia de FPS más allá de la tasa de refresco del LCD.
  • Una base práctica que puedes ampliar con alarmas, registro de datos, estimaciones de estado de carga o indicadores de dirección de carga/descarga.

Audiencia: Principiantes en Arduino, estudiantes, makers y aficionados que trabajan con pequeños proyectos solares o de baterías; Nivel: Principiante a intermedio

Arquitectura/flujo: Arduino lee el voltaje de la batería desde un divisor resistivo y la corriente desde la salida analógica del ACS712, convierte ambos valores ADC a unidades de ingeniería y luego actualiza la LCD HD44780 de 16×2 cada 200–500 ms con el estado en vivo de voltaje/corriente.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 4 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este es un prototipo educativo de monitoreo de bajo voltaje, no un instrumento certificado para baterías.

Límites y precauciones importantes:

  • Trabaja solo con montajes educativos de CC de bajo voltaje.
  • No conectes el pin analógico de Arduino directamente a una batería; usa siempre el divisor de voltaje indicado.
  • No uses este tutorial directamente en grandes arreglos solares, bancos de baterías de alta corriente, sistemas eléctricos de vehículos o equipos conectados a la red.
  • La ruta de corriente y la capacidad nominal de terminales del módulo ACS712 deben coincidir con tu corriente real. No excedas la capacidad del módulo.
  • Las baterías pueden entregar una alta corriente de falla incluso a bajo voltaje:
  • evita cortocircuitos
  • coloca un fusible en la línea de batería si es posible
  • mantén aislados los conductores expuestos
  • Si tu sistema solar incluye un controlador de carga, comprende el cableado del controlador antes de insertar el sensor de corriente en serie.
  • No confíes solo en este prototipo para proteger una batería contra sobrecarga, descarga profunda, sobrecalentamiento o fallas de cableado.
  • Durante la calibración al arranque, asegúrate de que no circule corriente por el ACS712, de lo contrario el punto cero será incorrecto y la lectura de corriente tendrá sesgo.

Prerrequisitos

Antes de empezar, deberías tener:

  • Un ordenador con Arduino CLI instalado
  • Un cable USB para el Arduino UNO R3
  • Familiaridad básica con:
  • cargar sketches
  • leer etiquetas de pines
  • usar un multímetro
  • Un pequeño sistema solar-batería de bajo voltaje bajo prueba, como por ejemplo:
  • una batería sellada de plomo-ácido de 12 V con un pequeño controlador de carga solar
  • una configuración educativa LiFePO4 de 12 V con protección adecuada
  • una fuente DC de banco actuando como simulador solar para pruebas

Para una primera construcción, se recomienda encarecidamente probar con una fuente de banco y una pequeña carga DC antes de conectarlo a un sistema solar exterior real.

Materiales

Usa exactamente este modelo de dispositivo como núcleo del proyecto:

Arduino UNO R3 (ATmega328P) + divisor de voltaje + sensor de corriente ACS712 + LCD HD44780 de 16×2

Lista de piezas recomendada:

Elemento Especificación sugerida Notas
Placa Arduino Arduino UNO R3 (ATmega328P) Requerido
LCD Compatible con HD44780 de 16×2, interfaz paralela Requerido
Sensor de corriente Módulo ACS712, preferiblemente versión de 5 A La versión de 5 A ofrece mejor sensibilidad
Resistencia R1 del divisor de voltaje 47 kOhm, 1/4 W Resistencia superior desde el positivo de batería al pin analógico
Resistencia R2 del divisor de voltaje 10 kOhm, 1/4 W Resistencia inferior desde el pin analógico a GND
Potenciómetro de contraste LCD Trimmer de 10 kOhm Requerido para texto visible
Protoboard y cables jumper Estándar Requerido
Batería DC pequeña bajo prueba Típicamente 12 V Mantener dentro del rango de entrada del divisor
Carga DC pequeña para pruebas Lámpara de 12 V o carga resistiva Útil para validación
Multímetro Digital Muy recomendado

¿Por qué estos valores de resistencia?

Con 47 kOhm y 10 kOhm, la entrada analógica ve:

V_A0 = V_battery x (10 / (47 + 10)) = V_battery x 0.1754

Eso mantiene la entrada analógica por debajo de unos 5 V hasta aproximadamente 28.5 V. Esto es cómodo para sistemas de 12 V y ofrece margen útil para voltajes de carga. Aun así, este tutorial está pensado para sistemas educativos de baterías de bajo voltaje, no para instalaciones grandes o de alta energía.

Configuración/Conexión

Concepto de medición

Este monitor usa dos canales analógicos:

  • A0 lee el voltaje de la batería a través del divisor resistivo
  • A1 lee la salida analógica del ACS712

La LCD muestra:

  • voltaje de batería
  • corriente
  • potencia
  • un mensaje simple de estado como CHARGING, DISCHARGE o IDLE

Elección importante de la ruta de corriente

Elige una ruta de corriente para monitorear con el ACS712:

  • Opción A: corriente de carga
  • Coloca el ACS712 en serie entre la salida del controlador de carga y la ruta del positivo de batería, si tu configuración educativa de baja potencia lo permite.
  • Opción B: corriente de carga/consumo
  • Coloca el ACS712 en serie entre el positivo de batería y la carga DC.

Para una construcción de principiante, la Opción B suele ser más fácil y segura de entender:
– corriente cercana a cero cuando la carga está apagada
– la corriente aumenta cuando la carga está encendida

Conexiones de Arduino y LCD

Usa el cableado estándar de LiquidCrystal a continuación.

Función Pin LCD Conectar a
VSS 1 Arduino GND
VDD 2 Arduino 5V
VO 3 Pin central del potenciómetro de 10 kOhm
RS 4 Arduino D7
RW 5 Arduino GND
E 6 Arduino D8
D4 11 Arduino D9
D5 12 Arduino D10
D6 13 Arduino D11
D7 14 Arduino D12
A (retroiluminación +) 15 5V a través de la resistencia del módulo si es necesario
K (retroiluminación -) 16 GND

Cableado del potenciómetro:
– un pin exterior -> 5V
– otro pin exterior -> GND
– pin central -> pin 3 del LCD (VO)

Conexiones de medición analógica

Divisor de voltaje

  • Positivo de batería -> resistencia de 47 kOhm -> nodo -> A0
  • Mismo nodo -> resistencia de 10 kOhm -> GND
  • Negativo de batería -> Arduino GND

Esta masa común es esencial. Sin ella, la lectura del ADC no tendrá sentido.

ACS712

La mayoría de los módulos ACS712 tienen:
VCC
GND
OUT
– dos terminales de tornillo para la ruta de corriente medida

Conecta:
VCC -> Arduino 5V
GND -> Arduino GND
OUT -> Arduino A1

Para la ruta de corriente:
– conecta el cable positivo monitoreado de modo que la corriente fluya por los terminales del ACS712
– observa la flecha del módulo o la dirección impresa si está presente
– si el signo de la corriente parece invertido, intercambia los terminales de alta corriente o invierte el signo en software

Alimentación del Arduino durante la configuración

Para el desarrollo inicial:
– alimenta el Arduino por USB
– mantén conectados la medición de batería y el sensor de corriente con masa común

Después de las pruebas, más adelante puedes alimentar el Arduino desde una fuente regulada de 5 V estable, pero no alimentes el UNO directamente desde un panel solar no regulado.

Código validado

Sketch completo de Arduino: solar-battery-lcd-monitor.ino

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Analog pins
const uint8_t PIN_BATTERY = A0;
const uint8_t PIN_CURRENT = A1;

// ADC reference
const float ADC_REF_VOLTAGE = 5.0;
const int ADC_MAX = 1023;

// Voltage divider values
const float R1 = 47000.0; // ohms, top resistor
const float R2 = 10000.0; // ohms, bottom resistor

// ACS712 settings
// Set according to your module:
// 5A module  -> 185.0 mV/A
// 20A module -> 100.0 mV/A
// 30A module -> 66.0  mV/A
const float ACS712_MV_PER_AMP = 185.0;

// Calibration values
float currentZeroVoltage = 2.50;   // Will be refined during startup calibration
float voltageCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed
float currentCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed

// Sampling
const int NUM_SAMPLES_VOLTAGE = 20;
const int NUM_SAMPLES_CURRENT = 100;

// Timing
unsigned long lastDisplayUpdate = 0;
const unsigned long DISPLAY_INTERVAL_MS = 500;

// Optional status thresholds
const float LOW_BATTERY_VOLTAGE = 11.8;
const float IDLE_CURRENT_THRESHOLD = 0.08;

float readAverageVoltageAtPin(uint8_t pin, int samples) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delay(2);
  }
  float adc = sum / (float)samples;
  return (adc * ADC_REF_VOLTAGE) / ADC_MAX;
}

float readBatteryVoltage() {
  float dividedVoltage = readAverageVoltageAtPin(PIN_BATTERY, NUM_SAMPLES_VOLTAGE);
  float batteryVoltage = dividedVoltage * ((R1 + R2) / R2);
  batteryVoltage *= voltageCalibrationFactor;
  return batteryVoltage;
}

float readCurrent() {
  float sensorVoltage = readAverageVoltageAtPin(PIN_CURRENT, NUM_SAMPLES_CURRENT);
  float deltaVoltage = sensorVoltage - currentZeroVoltage; // volts
  float current = (deltaVoltage * 1000.0) / ACS712_MV_PER_AMP; // amps
  current *= currentCalibrationFactor;
  return current;
}

void calibrateCurrentZero() {
  // Assumes no current is flowing through ACS712 during startup.
  float total = 0.0;
  const int rounds = 10;
  for (int i = 0; i < rounds; i++) {
    total += readAverageVoltageAtPin(PIN_CURRENT, 50);
    delay(20);
  }
  currentZeroVoltage = total / rounds;
}
// ...

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Analog pins
const uint8_t PIN_BATTERY = A0;
const uint8_t PIN_CURRENT = A1;

// ADC reference
const float ADC_REF_VOLTAGE = 5.0;
const int ADC_MAX = 1023;

// Voltage divider values
const float R1 = 47000.0; // ohms, top resistor
const float R2 = 10000.0; // ohms, bottom resistor

// ACS712 settings
// Set according to your module:
// 5A module  -> 185.0 mV/A
// 20A module -> 100.0 mV/A
// 30A module -> 66.0  mV/A
const float ACS712_MV_PER_AMP = 185.0;

// Calibration values
float currentZeroVoltage = 2.50;   // Will be refined during startup calibration
float voltageCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed
float currentCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed

// Sampling
const int NUM_SAMPLES_VOLTAGE = 20;
const int NUM_SAMPLES_CURRENT = 100;

// Timing
unsigned long lastDisplayUpdate = 0;
const unsigned long DISPLAY_INTERVAL_MS = 500;

// Optional status thresholds
const float LOW_BATTERY_VOLTAGE = 11.8;
const float IDLE_CURRENT_THRESHOLD = 0.08;

float readAverageVoltageAtPin(uint8_t pin, int samples) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delay(2);
  }
  float adc = sum / (float)samples;
  return (adc * ADC_REF_VOLTAGE) / ADC_MAX;
}

float readBatteryVoltage() {
  float dividedVoltage = readAverageVoltageAtPin(PIN_BATTERY, NUM_SAMPLES_VOLTAGE);
  float batteryVoltage = dividedVoltage * ((R1 + R2) / R2);
  batteryVoltage *= voltageCalibrationFactor;
  return batteryVoltage;
}

float readCurrent() {
  float sensorVoltage = readAverageVoltageAtPin(PIN_CURRENT, NUM_SAMPLES_CURRENT);
  float deltaVoltage = sensorVoltage - currentZeroVoltage; // volts
  float current = (deltaVoltage * 1000.0) / ACS712_MV_PER_AMP; // amps
  current *= currentCalibrationFactor;
  return current;
}

void calibrateCurrentZero() {
  // Assumes no current is flowing through ACS712 during startup.
  float total = 0.0;
  const int rounds = 10;
  for (int i = 0; i < rounds; i++) {
    total += readAverageVoltageAtPin(PIN_CURRENT, 50);
    delay(20);
  }
  currentZeroVoltage = total / rounds;
}

const char* stateText(float current, float batteryVoltage) {
  if (current > IDLE_CURRENT_THRESHOLD) {
    return "CHARGING";
  } else if (current < -IDLE_CURRENT_THRESHOLD) {
    return "DISCHARGE";
  } else if (batteryVoltage < LOW_BATTERY_VOLTAGE) {
    return "LOW BAT";
  } else {
    return "IDLE";
  }
}

void printPadded(String text, uint8_t width) {
  if (text.length() >= width) {
    lcd.print(text.substring(0, width));
  } else {
    lcd.print(text);
    for (uint8_t i = text.length(); i < width; i++) {
      lcd.print(' ');
    }
  }
}

void setup() {
  lcd.begin(16, 2);
  Serial.begin(9600);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Solar Battery");
  lcd.setCursor(0, 1);
  lcd.print("Monitor Start");
  delay(1200);

  calibrateCurrentZero();

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Zero I V=");
  lcd.print(currentZeroVoltage, 3);
  delay(1500);

  Serial.println("solar-battery-lcd-monitor");
  Serial.print("Current zero voltage = ");
  Serial.println(currentZeroVoltage, 4);
}

void loop() {
  if (millis() - lastDisplayUpdate >= DISPLAY_INTERVAL_MS) {
    lastDisplayUpdate = millis();

    float batteryVoltage = readBatteryVoltage();
    float current = readCurrent();
    float power = batteryVoltage * current;
    const char* state = stateText(current, batteryVoltage);

    // LCD line 1: Voltage and current
    lcd.setCursor(0, 0);
    String line1 = "V:";
    line1 += String(batteryVoltage, 2);
    line1 += " I:";
    line1 += String(current, 2);
    printPadded(line1, 16);

    // LCD line 2: Power and state
    lcd.setCursor(0, 1);
    String line2 = "P:";
    line2 += String(power, 1);
    line2 += " ";
    line2 += state;
    printPadded(line2, 16);

    // Serial output for validation
    Serial.print("Battery_V=");
    Serial.print(batteryVoltage, 3);
    Serial.print(", Current_A=");
    Serial.print(current, 3);
    Serial.print(", Power_W=");
    Serial.print(power, 3);
    Serial.print(", State=");
    Serial.println(state);
  }
}

Notas sobre los valores de calibración

Puede que necesites pequeños ajustes después de comparar con un multímetro:

  • voltageCalibrationFactor
  • Ejemplo: si la LCD muestra 12.40 V pero el multímetro muestra 12.55 V, establece el factor cerca de 12.55 / 12.40 = 1.012
  • currentCalibrationFactor
  • Ejemplo: si la corriente conocida es 0.90 A pero la pantalla muestra 0.84 A, establece el factor cerca de 0.90 / 0.84 = 1.071

Para la primera carga, deja ambos en 1.000.

Comandos de compilación/carga/ejecución

Tabla de comandos

Paso Comando
Actualizar índice de placas arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno ~/arduino/solar-battery-lcd-monitor
Cargar sketch arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ~/arduino/solar-battery-lcd-monitor

Flujo de trabajo

  1. Crea una carpeta de proyecto:
  2. ~/arduino/solar-battery-lcd-monitor/
  3. Guarda el sketch como:
  4. ~/arduino/solar-battery-lcd-monitor/solar-battery-lcd-monitor.ino
  5. Ejecuta los comandos siguientes en orden.
arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli compile --fqbn arduino:avr:uno ~/arduino/solar-battery-lcd-monitor
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ~/arduino/solar-battery-lcd-monitor

Reemplaza <PORT> por el puerto de tu placa, por ejemplo:
– Linux: /dev/ttyACM0
– macOS: /dev/cu.usbmodem14101
– Windows: COM3

Comportamiento en ejecución

Después de la carga:
1. La LCD debería mostrar brevemente un mensaje de inicio.
2. El sketch calibra el voltaje de corriente cero del ACS712.
3. La LCD comienza a actualizarse cada 0.5 segundos.
4. Abre un monitor serie a 9600 baud si quieres registros de texto para validación.

Validación paso a paso

Usa un multímetro y una pequeña configuración de prueba de baja potencia. Realiza las comprobaciones en este orden.

1. Comprobación de LCD e inicio

Acción
– Alimenta el Arduino por USB.
– Deja abierta la ruta de corriente del ACS712 o sin corriente activa.
– Ajusta lentamente el potenciómetro de contraste de la LCD.

Observación esperada
– Aparece un mensaje de inicio legible.
– Después del inicio, el monitor muestra campos de voltaje/corriente en lugar de bloques aleatorios.

Condición de aprobación
– El texto es visible y estable en ambas líneas de la LCD.

2. Comprobación de calibración de corriente cero

Acción
– Asegúrate de que no circule corriente por el ACS712 durante el reinicio o encendido.
– Observa la salida serie o la LCD después del inicio.

Observación esperada
– La corriente debería estar cerca de 0.00 A, con pequeño ruido alrededor de cero.
– El estado normalmente debería ser IDLE, salvo que un voltaje bajo de batería dispare LOW BAT.

Condición de aprobación
– La corriente en reposo permanece pequeña y no deriva de forma exagerada, típicamente unas pocas centésimas de amperio alrededor de cero.

3. Validación de voltaje de batería

Acción
– Mide directamente el voltaje de la batería con un multímetro.
– Compáralo con la LCD y la salida serie.

Observación esperada
– El voltaje de batería mostrado es cercano a la lectura del multímetro.
– Pequeñas diferencias son normales antes de la calibración.

Condición de aprobación
– La lectura es lógicamente correcta y lo bastante cercana como para que un pequeño voltageCalibrationFactor pueda alinearla.
– Si el multímetro lee 12.6 V y la pantalla dice 6 V o 20 V, el divisor o la masa están mal.

4. Comprobación de dirección y magnitud de corriente

Acción
– Conecta una pequeña carga DC a través del ACS712.
– Enciende y apaga la carga.
– Si estás probando una ruta de carga, expón el panel a la luz o usa una fuente de banco a través de un controlador adecuado.

Observación esperada
– La corriente cambia de forma apreciable cuando cambia el estado de carga o consumo.
– El signo debe ser coherente con la dirección de cableado elegida.
– La potencia debe cambiar en la misma dirección que la corriente.

Condición de aprobación
– Encender la carga produce una lectura de corriente repetible.
– Si el signo es el contrario al que deseas, intercambia los terminales de corriente del ACS712 o invierte la convención de signo.

5. Comprobación del mensaje de estado

Acción
– Observa la segunda línea de la LCD mientras cambias las condiciones de funcionamiento:
– sin corriente
– carga activa
– corriente de carga activa
– voltaje bajo de batería si está disponible

Observación esperada
CHARGING para corriente positiva
DISCHARGE para corriente negativa
IDLE cerca de corriente cero
LOW BAT cuando el voltaje está por debajo del umbral y la corriente está cerca de cero

Condición de aprobación
– La etiqueta de estado sigue lógicamente la condición eléctrica medida.

Solución de problemas

Síntoma Causa probable Solución
Retroiluminación LCD encendida pero sin texto Contraste no ajustado o cableado LCD incorrecto Ajusta el potenciómetro; vuelve a comprobar los pines RS, E, D4-D7
Caracteres aleatorios en LCD Mapeo de pines incorrecto en el código o cables flojos Haz coincidir exactamente los pines del código con las conexiones físicas
El voltaje de batería marca 0 V Nodo del divisor no conectado a A0 o no hay masa común Revisa el nodo A0 y el negativo de batería a Arduino GND
El voltaje de batería es demasiado alto o bajo Valores de resistencias incorrectos o fórmula del divisor no coincide Confirma la colocación de 47 kOhm y 10 kOhm; verifica las constantes del código
La corriente siempre está cerca de cero Salida del ACS712 no conectada, no hay ruta de corriente o método de prueba incorrecto Comprueba A1, VCC, GND y que la corriente de la carga realmente pase por los terminales del sensor
Valor de corriente inestable Ruido del sensor, cableado deficiente o ruido de carga conmutada Acorta cables, mejora la masa, promedia más muestras
Signo de corriente invertido Sensor instalado en dirección opuesta Intercambia terminales de corriente o multiplica la corriente por -1 en el código
Falla la carga del sketch Puerto incorrecto o falta el núcleo AVR Instala el núcleo arduino:avr y verifica <PORT>
La pantalla se reinicia cuando cambia la carga Inestabilidad en la alimentación o fuente ruidosa compartida Usa 5 V estables para Arduino y asegúrate de tener masa común

Mejoras

  • Mejor calidad de medición
  • Añade un pequeño capacitor, por ejemplo de 100 nF, desde A0 a GND cerca del Arduino para reducir el ruido del divisor.
  • Aumenta el promedio de corriente si tu carga es estable.
  • Añade un modo de calibración por software que almacene factores en EEPROM.

  • Comportamiento de campo más útil

  • Añade un zumbador o advertencia LED para batería baja.
  • Añade una segunda página en la LCD que alterne entre:
    • voltaje/corriente/potencia
    • voltaje mínimo/máximo
    • estimación acumulada de amperio-hora con el tiempo
  • Añade un pulsador para alternar entre etiquetas de monitoreo de corriente de carga y de consumo.

  • Empaquetado y despliegue

  • Mueve el circuito de la protoboard a una placa perforada.
  • Instala el monitor en una pequeña caja plástica cerca de una caja de batería de bajo voltaje.
  • Usa terminales de tornillo para la entrada del divisor y la ruta de corriente del ACS712 para hacer reutilizable el prototipo.

Lista de comprobación final

  • [ ] Usé Arduino UNO R3 (ATmega328P).
  • [ ] Usé un divisor de voltaje de 47 kOhm / 10 kOhm para el voltaje de batería.
  • [ ] Conecté juntos el negativo de batería, Arduino GND, LCD GND y ACS712 GND.
  • [ ] Conecté el pin ACS712 OUT a A1.
  • [ ] Conecté el nodo del divisor de voltaje a A0.
  • [ ] Cableé la LCD HD44780 de 16×2 a los pines D7, D8, D9, D10, D11, D12 como se usa en el sketch.
  • [ ] Ajusté el potenciómetro de contraste de la LCD hasta que el texto se hizo visible.
  • [ ] Compilé con:
  • arduino-cli compile --fqbn arduino:avr:uno ~/arduino/solar-battery-lcd-monitor
  • [ ] Cargué con:
  • arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ~/arduino/solar-battery-lcd-monitor
  • [ ] Encendí con sin corriente a través del ACS712 para la calibración de cero.
  • [ ] Comparé el voltaje de batería con un multímetro.
  • [ ] Probé la respuesta de corriente usando una pequeña carga DC o una condición de carga controlada.
  • [ ] Actualicé los factores de calibración solo después de comparar mediciones.
  • [ ] Entiendo que este es un monitor educativo básico, no un dispositivo de protección certificado.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué mide el monitor solar de batería basado en Arduino respecto a la batería de 12 V?




Pregunta 2: ¿Qué sensor se utiliza para medir la corriente de carga o consumo en el sistema?




Pregunta 3: ¿En qué tipo de pantalla se muestran los valores medidos?




Pregunta 4: ¿Cuál es la frecuencia de actualización aproximada de las lecturas locales?




Pregunta 5: ¿Qué latencia de visualización típica ofrece el sistema?




Pregunta 6: ¿Qué indicaría una corriente positiva de +0.3 A a +1.8 A bajo sol directo?




Pregunta 7: ¿Qué rango de voltaje se menciona como advertencia de batería de plomo-ácido baja mientras alimenta una carga?




Pregunta 8: ¿Cómo debe colocarse el ACS712 para medir la corriente real de consumo de una carga?




Pregunta 9: ¿Cuál es un ejemplo de consumo esperado mencionado para una bomba pequeña?




Pregunta 10: ¿Cuál de las siguientes es una salida esperada en la LCD del proyecto?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: termostato de incubadora Arduino UNO

Caso práctico: termostato de incubadora Arduino UNO — hero

case-device-block-diagram,
.prometeo-educational-note,
.prometeo-device-postcode-section,
.prometeo-device-section-card {
margin: 2.4rem 0;
padding: 1.45rem 1.55rem;
border: 1px solid rgba(148, 163, 184, 0.30);
border-radius: 14px;
background:
linear-gradient(135deg, rgba(30, 41, 59, 0.50), rgba(15, 23, 42, 0.18)),
rgba(17, 24, 39, 0.48);
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.20);
}
.case-objective > h2:first-of-type,
.case-device-block-diagram > h2:first-of-type,
.prometeo-device-postcode-section > h2:first-of-type,
.prometeo-device-section-card > h2:first-of-type {
margin-top: 0;
}
.prometeo-device-section-card > :last-child,
.case-objective > :last-child,
.case-device-block-diagram > :last-child,
.prometeo-device-postcode-section > :last-child {
margin-bottom: 0;
}
.prometeo-device-section-card.prometeo-device-section-card-code {
border-left: 4px solid rgba(56, 189, 248, 0.86);
background:
linear-gradient(135deg, rgba(8, 47, 73, 0.42), rgba(15, 23, 42, 0.18)),
rgba(15, 23, 42, 0.58);
}
.prometeo-device-section-card.prometeo-device-section-card-compact {
padding: 1.2rem 1.35rem;
}
.prometeo-device-section-card pre,
.case-objective pre,
.case-device-block-diagram pre,
.prometeo-device-postcode-section pre {
max-width: 100%;
}
.prometeo-device-postcode-section .prometeo-device-flow-item {
background:
linear-gradient(135deg, rgba(15, 23, 42, 0.50), rgba(30, 41, 59, 0.28)),
rgba(15, 23, 42, 0.28);
}
@media print {
.case-objective,
.case-device-block-diagram,
.prometeo-educational-note,
.prometeo-device-postcode-section,
.prometeo-device-section-card {
background: #fff;
color: #111827;
box-shadow: none;
break-inside: avoid;
page-break-inside: avoid;
}
}

Objetivo y caso de uso

Qué construirás: Un controlador básico de termostato para una pequeña incubadora educativa o cámara térmica usando un Arduino UNO R3, sensor de temperatura LM35, relé de 1 canal y LCD HD44780 de 16×2. Lee la temperatura aproximadamente 2-5 veces por segundo, muestra valores en vivo en la LCD y enciende/apaga un calentador con histéresis para mantener una banda objetivo con una estabilidad aproximada de 1-2°C, según el tamaño del recinto y la potencia del calentador.

Por qué importa / Casos de uso

  • Prototipo de incubadora educativa: Demuestra control en lazo cerrado en un montaje realista de aula, como una maqueta de incubadora de huevos pequeña o una caja de germinación de semillas.
  • Cámara térmica DIY para ejercicios de laboratorio: Mantiene un recinto pequeño cerca de un punto de ajuste elegido, por ejemplo 30-37°C para demostraciones de materiales o electrónica.
  • Práctica de control con relé: Muestra cómo una salida digital acciona una carga real y por qué la histéresis evita el golpeteo rápido del relé y el desgaste prematuro.
  • Integración de sensor a actuador: Combina sensado analógico, visualización en tiempo real y control de actuador en un flujo de trabajo embebido similar a muchos fundamentos industriales.
  • Monitoreo con conciencia de fallos: La LCD y los registros serie ayudan a detectar sobreimpulso, calentamiento lento, sensores desconectados o lecturas inestables con una latencia de respuesta típicamente inferior a 500 ms por ciclo de actualización.

Resultado esperado

  • Un termostato funcional que enciende el calentador ON por debajo de un umbral inferior y lo apaga OFF por encima de un umbral superior.
  • Lectura en vivo en la LCD de la temperatura actual y del estado del calentador, actualizada cada 200-500 ms.
  • Control estable de temperatura en una cámara pequeña, típicamente dentro de ±0.5 a ±1°C después del calentamiento inicial cuando la histéresis está ajustada correctamente.
  • Salida serie para calibración y depuración, útil para comparar las lecturas del LM35 con un termómetro de referencia.
  • Una base clara para futuras mejoras como botones para ingresar el punto de ajuste, alarmas con zumbador o registro de datos.

Audiencia: principiantes en Arduino, estudiantes y educadores que construyen sistemas de control de nivel inicial; Nivel: principiante a intermedio

Arquitectura/flujo: voltaje analógico del LM35 → conversión ADC del Arduino UNO → cálculo de temperatura y lógica de umbral/histéresis → el relé conmuta el calentador → la LCD 16×2 y el monitor serie muestran la temperatura actual y el estado del calentador; no aplican FPS ni %GPU porque este es un proyecto de control basado en microcontrolador.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

voltaje analógico del LM35

conversión ADC del Arduino UNO

cálculo de temperatura y lógica de umbral…

el relé conmuta el calentador

la LCD 16×2 y el monitor serie muestran l…

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Sketch

arduino-cli compile

Upload

Prueba funcional

Resumen conceptual de las herramientas usadas para comprobar el material publicado.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 3 tablas y 3 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este proyecto es un prototipo educativo de termostato, no un producto certificado de control de temperatura.

Límites importantes y reglas de seguridad:

  • No lo consideres a prueba de fallos. Un error de software, fallo de cableado, relé trabado, sensor defectuoso o conexión floja puede causar sobrecalentamiento o pérdida de control.
  • Prefiere calentadores de bajo voltaje para trabajos de principiantes. Un elemento calefactor resistivo de bajo voltaje o una lámpara alimentada por una fuente externa apropiada es mucho más seguro para aprender.
  • Advertencia sobre tensión de red: Si tu relé conmuta calefacción alimentada por red eléctrica, eso introduce riesgo de descarga y de incendio. Los principiantes no deben cablear cargas de red por sí solos. Usa supervisión calificada y envolventes aisladas adecuadas, fusibles, alivio de tensión y prácticas eléctricas legales.
  • No alimentes el calentador desde el Arduino. El UNO es solo para señales de control y electrónica de baja potencia.
  • Nunca dejes el prototipo sin supervisión durante las pruebas, especialmente dentro de una cámara cerrada.
  • Mantén el sensor alejado del contacto directo con el calentador. Si el LM35 toca el calentador, puede leer la temperatura de la superficie del calentador en lugar de la temperatura del aire de la cámara, causando un mal control.
  • Espera una precisión limitada. El LM35, el control simple por histéresis, la conmutación por relé y el diseño del recinto limitan la estabilidad.
  • Sin certificación de seguridad: Este montaje no es para uso industrial, producción de alimentos, incubación comercial ni ninguna situación en la que un fallo pueda causar daños, incendio o daños a la propiedad.

Prerrequisitos

Antes de comenzar, deberías sentirte cómodo con:

  • Cargar un sketch a un Arduino UNO R3 (ATmega328P).
  • Leer un diagrama de protoboard en formato de texto.
  • Usar un cable USB y un monitor serie.
  • Seguridad básica en cableado DC.
  • Entender que un relé es un interruptor, no una fuente de alimentación.

Preparación recomendada:

  1. Instala Arduino CLI en tu computadora.
  2. Ten un cable USB en buen estado para el UNO.
  3. Decide si tu calentador es:
  4. un elemento calefactor DC de bajo voltaje controlado a través del relé, o
  5. un calentador alimentado por red, lo cual no se recomienda para principiantes sin supervisión calificada.

Para una construcción educativa básica, se prefiere fuertemente un calentador pequeño de bajo voltaje.

Materiales

Usa exactamente este conjunto de hardware como familia y modelo principal del dispositivo:

  • Arduino UNO R3 (ATmega328P) + sensor de temperatura analógico LM35 + módulo de relé de 1 canal + LCD HD44780 de 16×2

Lista completa sugerida de piezas:

Elemento Modelo exacto/recomendado Cant. Notas
Placa microcontroladora Arduino UNO R3 (ATmega328P) 1 Requerido
Sensor de temperatura Sensor de temperatura analógico LM35 1 Requerido
Placa de relé Módulo de relé de 1 canal, bobina de 5 V, compatible con Arduino 1 Requerido
Pantalla LCD HD44780 de 16×2, interfaz paralela 1 Requerido
Potenciómetro Trimmer de 10 kOhm 1 Para contraste de la LCD
Resistencia 220 Ohm 1 Limitación de corriente de la retroiluminación de la LCD si es necesario
Protoboard Protoboard estándar sin soldadura 1 Para el cableado
Cables jumper Macho a macho 20+ Para todas las conexiones
Cable USB USB A a USB B 1 Para programar el UNO
Calentador/carga Pequeño calentador o lámpara de bajo voltaje usada como fuente de calor 1 Carga de prototipo educativo
Fuente externa para el calentador Adaptada al calentador 1 No alimentes el calentador desde el UNO
Recinto opcional Caja pequeña aislada 1 Para pruebas realistas de incubadora
Termómetro opcional Termómetro de referencia independiente 1 Útil para validación

Configuración/Conexión

Este proyecto usa:

  • LM35 para medición analógica de temperatura
  • módulo de relé para conmutación del calentador
  • LCD HD44780 de 16×2 en modo de 4 bits para mostrar el estado

Plan de pines

Usa exactamente esta asignación de pines de Arduino en el sketch de abajo:

  • Salida del LM35 -> A0
  • Entrada del relé -> D8
  • LCD RS -> D12
  • LCD EN -> D11
  • LCD D4 -> D5
  • LCD D5 -> D4
  • LCD D6 -> D3
  • LCD D7 -> D2

Cableado del LM35

Con la cara plana de un LM35 típico TO-92 mirando hacia ti y los pines hacia abajo, la disposición usual de pines es:

  1. Pin izquierdo -> +5V
  2. Pin central -> A0
  3. Pin derecho -> GND

Confirma siempre la hoja de datos de tu sensor o el etiquetado del proveedor antes de alimentarlo.

Cableado del módulo de relé

La mayoría de los módulos de relé de 1 canal tienen VCC, GND e IN.

  • VCC -> 5V
  • GND -> GND
  • IN -> D8

Los terminales del lado de carga del relé suelen ser:

  • COM
  • NO (Normalmente Abierto)
  • NC (Normalmente Cerrado)

Para un calentador que debe estar apagado por defecto, usa:

  • línea de la fuente de alimentación -> COM
  • entrada del calentador -> NO

El calentador regresa a su ruta de retorno de alimentación según su propio circuito de potencia.

Cableado de la LCD en modo de 4 bits

Pines típicos de la LCD:

  1. VSS -> GND
  2. VDD -> 5V
  3. VO -> pin central del potenciómetro de 10 kOhm
  4. RS -> D12
  5. RW -> GND
  6. E -> D11
  7. D0 -> no conectado
  8. D1 -> no conectado
  9. D2 -> no conectado
  10. D3 -> no conectado
  11. D4 -> D5
  12. D5 -> D4
  13. D6 -> D3
  14. D7 -> D2
  15. A o LED+ -> 5V a través de una resistencia de 220 Ohm si es necesario
  16. K o LED- -> GND

Potenciómetro para contraste:

  • un pin exterior -> 5V
  • el otro pin exterior -> GND
  • pin central -> pin VO 3 de la LCD

Reglas de alimentación y tierra

  • El UNO, LM35, lado de entrada del relé y la LCD deben compartir una tierra común.
  • No alimentes el calentador desde el pin 5 V del Arduino.
  • Usa una fuente de alimentación separada para el calentador si la carga lo requiere.
  • Si controlas cualquier cosa más allá de cargas educativas de bajo voltaje, obtén supervisión calificada.

Lógica de control usada en este montaje

Este termostato usa una ventana de histéresis:

  • Encender el calentador on cuando la temperatura esté por debajo de 36.5 C
  • Apagar el calentador off cuando la temperatura esté por encima de 37.5 C

Eso evita el golpeteo del relé cerca del punto de ajuste. Puedes cambiar los umbrales en el código para tu recinto y calentador.

Código validado

lm35_incubator_thermostat.ino

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int LM35_PIN = A0;
const int RELAY_PIN = 8;

// Adjust these thresholds for your chamber.
// Hysteresis prevents rapid relay toggling.
const float TEMP_ON_C = 36.5;   // Heater turns ON below this
const float TEMP_OFF_C = 37.5;  // Heater turns OFF above this

// Set this to true if your relay module is active LOW.
// Many Arduino relay boards energize the relay when IN is LOW.
const bool RELAY_ACTIVE_LOW = true;

bool heaterOn = false;
unsigned long lastSampleMs = 0;
const unsigned long SAMPLE_INTERVAL_MS = 1000;

float readTemperatureC() {
  const int samples = 10;
  long sum = 0;

  for (int i = 0; i < samples; i++) {
    sum += analogRead(LM35_PIN);
    delay(5);
  }

  float averageAdc = sum / (float)samples;
  float voltage = averageAdc * (5.0 / 1023.0);

  // LM35 output is 10 mV per degree C
  float temperatureC = voltage * 100.0;
  return temperatureC;
}

void setRelay(bool on) {
  heaterOn = on;

  if (RELAY_ACTIVE_LOW) {
    digitalWrite(RELAY_PIN, on ? LOW : HIGH);
  } else {
    digitalWrite(RELAY_PIN, on ? HIGH : LOW);
  }
}

void updateControl(float temperatureC) {
  if (!heaterOn && temperatureC < TEMP_ON_C) {
    setRelay(true);
  } else if (heaterOn && temperatureC > TEMP_OFF_C) {
    setRelay(false);
  }
}
// ...

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int LM35_PIN = A0;
const int RELAY_PIN = 8;

// Adjust these thresholds for your chamber.
// Hysteresis prevents rapid relay toggling.
const float TEMP_ON_C = 36.5;   // Heater turns ON below this
const float TEMP_OFF_C = 37.5;  // Heater turns OFF above this

// Set this to true if your relay module is active LOW.
// Many Arduino relay boards energize the relay when IN is LOW.
const bool RELAY_ACTIVE_LOW = true;

bool heaterOn = false;
unsigned long lastSampleMs = 0;
const unsigned long SAMPLE_INTERVAL_MS = 1000;

float readTemperatureC() {
  const int samples = 10;
  long sum = 0;

  for (int i = 0; i < samples; i++) {
    sum += analogRead(LM35_PIN);
    delay(5);
  }

  float averageAdc = sum / (float)samples;
  float voltage = averageAdc * (5.0 / 1023.0);

  // LM35 output is 10 mV per degree C
  float temperatureC = voltage * 100.0;
  return temperatureC;
}

void setRelay(bool on) {
  heaterOn = on;

  if (RELAY_ACTIVE_LOW) {
    digitalWrite(RELAY_PIN, on ? LOW : HIGH);
  } else {
    digitalWrite(RELAY_PIN, on ? HIGH : LOW);
  }
}

void updateControl(float temperatureC) {
  if (!heaterOn && temperatureC < TEMP_ON_C) {
    setRelay(true);
  } else if (heaterOn && temperatureC > TEMP_OFF_C) {
    setRelay(false);
  }
}

void updateDisplay(float temperatureC) {
  lcd.setCursor(0, 0);
  lcd.print("Temp: ");
  lcd.print(temperatureC, 1);
  lcd.print((char)223); // degree symbol
  lcd.print("C   ");

  lcd.setCursor(0, 1);
  lcd.print("Set ");
  lcd.print(TEMP_ON_C, 1);
  lcd.print("-");
  lcd.print(TEMP_OFF_C, 1);
  lcd.print(" ");

  lcd.setCursor(12, 1);
  if (heaterOn) {
    lcd.print("ON ");
  } else {
    lcd.print("OFF");
  }
}

void printSerial(float temperatureC) {
  Serial.print("Temperature_C=");
  Serial.print(temperatureC, 2);
  Serial.print(", Heater=");
  Serial.println(heaterOn ? "ON" : "OFF");
}

void setup() {
  pinMode(RELAY_PIN, OUTPUT);

  // Start with heater off
  setRelay(false);

  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Incubator Ctrl");
  lcd.setCursor(0, 1);
  lcd.print("Starting...");

  Serial.begin(9600);
  delay(1500);
  lcd.clear();
}

void loop() {
  unsigned long now = millis();

  if (now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
    lastSampleMs = now;

    float temperatureC = readTemperatureC();
    updateControl(temperatureC);
    updateDisplay(temperatureC);
    printSerial(temperatureC);
  }
}

Patrón opcional de registro serie para comparación

Este no es un segundo programa. Es un ejemplo de cómo puede verse una ejecución correcta en el monitor serie mientras el sensor se calienta:

Temperature_C=29.86, Heater=ON
Temperature_C=30.10, Heater=ON
Temperature_C=31.02, Heater=ON
Temperature_C=34.88, Heater=ON
Temperature_C=36.42, Heater=ON
Temperature_C=37.61, Heater=OFF
Temperature_C=37.43, Heater=OFF
Temperature_C=36.28, Heater=ON

Interpretación:

  • El calentador permanece ON hasta que la lectura sube por encima de TEMP_OFF_C.
  • Permanece OFF hasta que la lectura baja por debajo de TEMP_ON_C.
  • Esto confirma el comportamiento de histéresis.

Comandos de compilación/carga/ejecución

Usa Arduino CLI, no PlatformIO.

Tabla de comandos

Tarea Comando
Actualizar índice de placas arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno ./lm35_incubator_thermostat
Cargar sketch arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ./lm35_incubator_thermostat
Abrir monitor serie arduino-cli monitor --port <PORT> --config baudrate=9600

Flujo de trabajo breve

  1. Crea una carpeta de proyecto llamada lm35_incubator_thermostat.
  2. Guarda el sketch como:
  3. lm35_incubator_thermostat/lm35_incubator_thermostat.ino
  4. Ejecuta los comandos en orden:
arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli compile --fqbn arduino:avr:uno ./lm35_incubator_thermostat
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ./lm35_incubator_thermostat
arduino-cli monitor --port <PORT> --config baudrate=9600
  1. Sustituye <PORT> por tu puerto serie real:
  2. Ejemplo en Linux: /dev/ttyACM0
  3. Ejemplo en Windows: COM4
  4. Ejemplo en macOS: /dev/cu.usbmodem14101

Validación paso a paso

Usa estos puntos de control en orden. Están agrupados para que la validación sea práctica y repetible.

1. Encendido y activación de la LCD

Qué hacer:
– Alimenta el UNO desde USB.
– Ajusta lentamente el potenciómetro de contraste de la LCD.

Observación esperada:
– La LCD primero muestra:
Incubator Ctrl
Starting...
– Luego se actualiza a la temperatura y al estado del calentador.

Condición de aprobación:
– La LCD es legible y continúa actualizándose una vez por segundo.
– Si la retroiluminación está encendida pero no aparece texto, el ajuste de contraste o el cableado necesita corrección.

2. Comprobación básica del sensor

Qué hacer:
– Deja el LM35 a temperatura ambiente durante 30-60 segundos.
– Abre el monitor serie a 9600 baudios.
– Compara el valor mostrado con las condiciones de la habitación o con un termómetro independiente si está disponible.

Observación esperada:
– La lectura debe ser plausible para la habitación, típicamente alrededor de 20-35 C según tu entorno.
– La salida serie debe imprimir una línea por segundo.

Condición de aprobación:
– La temperatura no permanece fija en 0.0 C, 50+ C en una habitación normal, ni salta aleatoriamente en cantidades muy grandes.
– Los valores de la LCD y del puerto serie deben coincidir estrechamente.

3. Prueba de calentamiento controlado

Qué hacer:
– Calienta suavemente el LM35 usando tus dedos, o muévelo cerca de aire caliente sin sobrecalentarlo.
– Observa la LCD y la salida serie.

Observación esperada:
– La temperatura medida sube gradualmente.
– Una vez que cruza 37.5 C, el estado del relé cambia a OFF si antes estaba en ON.

Condición de aprobación:
– La temperatura responde al calentamiento de una manera suave y creíble.
– El relé cambia de estado cuando se cruza el umbral.

4. Prueba de enfriamiento controlado

Qué hacer:
– Deja que el LM35 se enfríe de manera natural de vuelta hacia la temperatura ambiente.
– Si es necesario, retira la fuente de calor y espera varios minutos.

Observación esperada:
– La temperatura baja gradualmente.
– Cuando desciende por debajo de 36.5 C, el relé cambia a ON.

Condición de aprobación:
– El relé no golpetea rápidamente encendiéndose y apagándose cerca de una única temperatura exacta.
– Sigue la regla de histéresis: apagado por encima del umbral superior, encendido por debajo del umbral inferior.

5. Prueba de comportamiento de la cámara con carga calefactora real

Qué hacer:
– Coloca el LM35 en el mismo espacio de aire que el calentador dentro de un pequeño recinto.
– Ejecuta el sistema durante 10-20 minutos usando una configuración educativa de calentador de bajo voltaje.

Observación esperada:
– La temperatura aumenta cuando comienza el calentamiento.
– El relé finalmente apaga el calentador cerca del umbral superior.
– Luego la temperatura deriva hacia abajo hasta que el calentador vuelve a encenderse.

Condición de aprobación:
– El recinto cicla entre los dos umbrales.
– El sistema se comporta como un termostato, no como un interruptor de una sola vez.

Solución de problemas

Síntoma Causa probable Solución
Retroiluminación de la LCD encendida pero sin texto Contraste mal ajustado, RW no conectado a GND, cableado incorrecto de pines Ajusta el potenciómetro, verifica los pines 4/5/6/11/12/13/14 de la LCD, asegúrate de que RW esté a GND
La LCD muestra texto pero la temperatura es 0.0 C La salida del LM35 no llega a A0, orientación incorrecta del LM35 Vuelve a comprobar el pinout del sensor y la conexión a A0
La lectura de temperatura es demasiado alta o inestable Tierra flotante, cableado ruidoso, pinout incorrecto del sensor Confirma tierra común, acorta los cables del sensor, vuelve a comprobar la orientación del LM35
El relé nunca cambia de estado Polaridad incorrecta de entrada del relé Cambia RELAY_ACTIVE_LOW entre true y false
El relé hace clic pero el calentador no enciende Cableado incorrecto COM/NO en el lado de carga, problema en la fuente de alimentación del calentador Vuelve a comprobar el cableado de terminales del relé y la ruta de alimentación externa del calentador
El relé golpetea rápidamente Umbrales demasiado cercanos, cámara demasiado pequeña, sensor demasiado cerca del calentador Aumenta la separación de histéresis, aleja el sensor del calor radiante directo
La carga serie falla Puerto o placa seleccionados incorrectos Usa el <PORT> correcto y el FQBN arduino:avr:uno
La temperatura tiene demasiado sobreimpulso Calentador demasiado potente, flujo de aire deficiente, problema de ubicación del sensor Reduce la potencia del calentador, añade flujo de aire suave, mueve el sensor a una ubicación representativa del aire

Mejoras

Mejoras de control y medición

  • Punto de ajuste ajustable: Añade botones o un codificador rotatorio para que el rango objetivo pueda cambiarse sin editar el código.
  • Desfase de calibración: Añade un desfase por software si tu LM35 difiere de manera consistente de un termómetro de referencia por una pequeña cantidad.
  • Promediado más largo: Incrementa el conteo de muestras o usa un promedio móvil para reducir el ruido en pantalla.

Mejoras de recinto y diseño térmico

  • Mejor ubicación del sensor: Coloca el LM35 en la corriente de aire o en el centro de la cámara, sin tocar el calentador ni la pared de la cámara.
  • Circulación de aire: Un pequeño ventilador de bajo voltaje puede mejorar la uniformidad de la temperatura dentro de la incubadora.
  • Aislamiento: Un mejor aislamiento del recinto reduce el ciclado del relé y hace la cámara más estable.

Mejoras de usabilidad y confiabilidad

  • Indicación de alarma: Añade un zumbador o LED si la temperatura sube o baja mucho del rango previsto.
  • Tiempo mínimo de apagado del relé: Añade reglas de temporización para reducir el desgaste si tu calentador responde muy rápido.
  • Registro de datos: Envía datos serie a una PC para registrar tendencias de temperatura a lo largo del tiempo.

Lista de verificación final

Usa esta lista antes de considerar el proyecto terminado:

  • [ ] Usé exactamente Arduino UNO R3 (ATmega328P).
  • [ ] Usé exactamente sensor de temperatura analógico LM35 + módulo de relé de 1 canal + LCD HD44780 de 16×2.
  • [ ] El cableado de la LCD coincide con el mapeo de pines del sketch.
  • [ ] La salida del LM35 va a A0, con orientación correcta de alimentación y tierra.
  • [ ] La entrada del relé va a D8.
  • [ ] El UNO, el lado de entrada del relé, el LM35 y la LCD comparten una tierra común.
  • [ ] El calentador usa su propia ruta de alimentación adecuada y no se alimenta desde el UNO.
  • [ ] El sketch compila con:
  • [ ] arduino-cli core update-index
  • [ ] arduino-cli core install arduino:avr
  • [ ] arduino-cli compile --fqbn arduino:avr:uno ./lm35_incubator_thermostat
  • [ ] El sketch se carga con:
  • [ ] arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ./lm35_incubator_thermostat
  • [ ] La LCD muestra la temperatura y el estado del calentador.
  • [ ] El monitor serie imprime una lectura por segundo a 9600 baudios.
  • [ ] Al calentar el LM35 sube la temperatura mostrada.
  • [ ] El relé se apaga off por encima del umbral superior y se enciende on por debajo del umbral inferior.
  • [ ] La cámara cicla dentro de la banda de histéresis durante una prueba supervisada.
  • [ ] Entiendo que este es un prototipo educativo y no un controlador de incubadora a prueba de fallos.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué placa se utiliza en el controlador básico de termostato descrito?




Pregunta 2: ¿Qué sensor de temperatura se menciona en el artículo?




Pregunta 3: ¿Qué tipo de pantalla se usa para mostrar la temperatura en vivo?




Pregunta 4: ¿Con qué frecuencia aproximada se lee la temperatura?




Pregunta 5: ¿Qué componente acciona la carga del calentador?




Pregunta 6: ¿Para qué sirve la histéresis en este sistema?




Pregunta 7: ¿Qué comportamiento se espera del termostato funcional?




Pregunta 8: ¿Qué estabilidad aproximada puede lograrse según el tamaño del recinto y la potencia del calentador?




Pregunta 9: ¿Qué rango de temperatura se pone como ejemplo para una cámara térmica DIY?




Pregunta 10: ¿Qué tipo de proyecto educativo se menciona como caso de uso del sistema?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: corte por fuga de agua con Arduino UNO

Caso práctico: corte por fuga de agua con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un controlador básico de relé de corte por fuga de agua usando un Arduino UNO R3, sensor de fuga de agua, módulo de relé de 1 canal y zumbador piezoeléctrico. Cuando se detecta agua, el Arduino conmuta el relé en aproximadamente 10–50 ms para cortar un circuito de control de baja tensión conectado y activa de inmediato una alarma local.

Por qué importa / Casos de uso

  • Protección bajo fregadero: Detecta goteos bajo un fregadero de cocina o baño y corta una línea de válvula de 5–12 V o de habilitación de bomba antes de que el agua acumulada se extienda por el mueble.
  • Monitoreo del área de la lavadora: Coloca el sensor en una bandeja cerca de la lavadora para que una fuga en el suelo active un zumbador audible y abra o cierre el relé para deshabilitar una ruta de control de solenoide de agua.
  • Respuesta en sótano o cuarto de servicio: Detecta fugas tempranas cerca de un calentador de agua, filtro o tubería de sumidero y envía una señal rápida de corte mientras proporciona una alerta inmediata en el lugar.
  • Prototipo educativo de automatización: Aprende sensado digital, conmutación segura con relé y control orientado a eventos en un microcontrolador de 8 bits con latencia en reposo casi nula y normalmente <1% de carga de CPU del UNO para este bucle simple.

Resultado esperado

  • Un sketch de Arduino que lee continuamente el sensor de fuga y cambia el estado del relé tan pronto como se detecta agua.
  • Una alarma con zumbador que se activa al mismo tiempo que el evento de corte para una notificación local clara.
  • Un prototipo de bajo costo adecuado para pruebas en banco con electrónica de 5 V y circuitos de enclavamiento de válvula, bomba o controlador de baja tensión.
  • Un flujo de respuesta repetible con temporización práctica: desde el humedecimiento del sensor hasta la acción del relé en decenas de milisegundos, sin uso de GPU y sin dependencia de FPS.

Audiencia: principiantes de Arduino, makers y estudiantes de automatización de edificios; Nivel: Principiante

Arquitectura/flujo: El sensor de fuga de agua detecta humedad → Arduino UNO lee la entrada digital/con umbral en el bucle principal → el relé se energiza o desenergiza según la elección de cableado a prueba de fallos → el zumbador suena hasta que la fuga desaparece o el sistema se reinicia.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 4 tablas y 3 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Arduino/arduino-cli compile, 1 C/C++ static checks, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este proyecto es un prototipo educativo, no un sistema certificado de protección de edificios. Trátalo como un controlador de aprendizaje y una ayuda de respuesta a fugas a escala de banco.

Límites y precauciones importantes:

  • No conectes tensión de red directamente a menos que estés cualificado para diseñar y probar sistemas de relé seguros para red eléctrica. Para uso de principiantes, mantén el relé solo en un circuito de baja tensión.
  • El módulo de relé de este tutorial debe usarse para conmutar:
  • cableado de control de válvula de baja tensión
  • una entrada de habilitación de baja tensión
  • una carga de demostración
    no equipos domésticos de fontanería de CA a menos que estén correctamente diseñados.
  • El agua y la electrónica deben mantenerse físicamente separadas:
  • solo el sensor de fuga debe exponerse al agua
  • mantén secos el Arduino, la placa de relé, el cable USB y el zumbador
  • Un sensor de fuga corroído o contaminado puede activar falsas alarmas o no detectar correctamente. Se requiere inspección periódica.
  • La alimentación USB desde un ordenador es aceptable para pruebas de laboratorio, pero una instalación en campo requeriría un diseño de alimentación y una estrategia de encapsulado más deliberados.
  • Este diseño no tiene garantía certificada a prueba de fallos, ni respaldo por batería, ni detección de manipulación.
  • Si se usa cerca de bombas, válvulas o actuadores de mayor corriente, confirma que los contactos del relé estén clasificados para la carga real y que las cargas inductivas tengan la supresión adecuada.
  • Nunca confíes solo en este prototipo para prevenir daños materiales en entornos de alto riesgo.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

El sensor de fuga de agua detecta humedad

Arduino UNO lee la entrada digital/con um…

el relé se energiza o desenergiza según l…

el zumbador suena hasta que la fuga desap…

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Sketch

arduino-cli compile

Upload

Prueba funcional

Resumen conceptual de las herramientas usadas para comprobar el material publicado.

Requisitos previos

Antes de empezar, deberías tener:

  • Familiaridad básica con:
  • conectar cables jumper a los pines del Arduino
  • seleccionar una placa y un puerto serie
  • cargar un sketch de Arduino
  • Un ordenador con:
  • Arduino CLI instalado
  • cable USB para el Arduino UNO R3
  • Un espacio de trabajo seco y un pequeño recipiente o toalla de papel para pruebas controladas con agua
  • Una carga o lazo de control de baja tensión para conectar a los contactos del relé con fines de demostración
  • Ejemplos:
    • una línea de control de válvula de 12 V DC
    • una pequeña línea de habilitación de bomba DC
    • una lámpara LED de prueba alimentada por separado
  • Para un laboratorio de principiantes, un LED o zumbador en el lado de contactos del relé es más seguro que conectar al principio cualquier equipo real de fontanería

Materiales

Usa exactamente esta combinación de familia/modelo de dispositivos:

Arduino UNO R3 (ATmega328P) + sensor de fuga de agua + módulo de relé de 1 canal + zumbador piezoeléctrico

Lista de materiales recomendada:

Elemento Tipo exacto/recomendado Propósito
Controlador principal Arduino UNO R3 (ATmega328P) Ejecuta la lógica de detección de fugas y del relé
Sensor Módulo de sensor de fuga de agua con salida analógica y/o digital Detecta presencia de agua en pistas expuestas
Salida de relé Módulo de relé de 1 canal, bobina de 5 V, entrada compatible con Arduino Conmuta el circuito de control externo de corte
Alarma Zumbador piezoeléctrico, compatible con 5 V Advertencia audible local
Cableado Cables jumper macho-macho Conexiones entre módulos
Cable USB USB A a USB B Alimentación y programación
Carga de prueba Lámpara de baja tensión, módulo LED o lazo de control de válvula deshabilitado Demostración segura de la acción del relé
Opcional Toalla pequeña, vaso, pulverizador Pruebas controladas con agua
Opcional Protoboard Cableado más limpio del zumbador si es necesario

Notas sobre la combinación exacta de modelos

  • El Arduino UNO R3 (ATmega328P) es el único controlador usado aquí.
  • El sensor de fuga de agua puede exponer:
  • AO: salida analógica
  • DO: salida digital con umbral desde un comparador integrado
  • En este tutorial, usaremos la salida analógica para un filtrado más estable y fácil de entender.
  • Se asume que el módulo de relé tiene:
  • VCC
  • GND
  • IN
  • terminales de tornillo como COM, NO y NC
  • El zumbador piezoeléctrico se trata como un zumbador simple de dos cables.

Configuración/Conexión

Este proyecto tiene dos partes:

  1. Lado lógico: Arduino, sensor, entrada del relé, zumbador
  2. Lado de contactos del relé: el circuito externo de baja tensión que deseas interrumpir o habilitar

Asignación de pines usada en este tutorial

Pin de Arduino UNO Pin del dispositivo conectado Función
5V VCC del sensor Alimentación del sensor de fuga
GND GND del sensor Tierra común
A0 AO del sensor Lectura analógica de fuga
5V VCC del relé Alimentación del módulo de relé
GND GND del relé Tierra común
D8 IN del relé Señal de control del relé
D9 Positivo del zumbador Pin de accionamiento del zumbador
GND Negativo del zumbador Retorno del zumbador

Pasos de conexión del sensor

  1. Conecta VCC del sensor de fuga de agua a 5V en el Arduino.
  2. Conecta GND del sensor a GND en el Arduino.
  3. Conecta AO del sensor a A0 en el Arduino.
  4. Si tu sensor también tiene DO, déjalo sin usar en este tutorial.

Pasos de conexión del módulo de relé

  1. Conecta VCC del relé a 5V del Arduino.
  2. Conecta GND del relé a GND del Arduino.
  3. Conecta IN del relé a D8 del Arduino.

Pasos de conexión del zumbador piezoeléctrico

  1. Conecta el terminal positivo del zumbador a D9.
  2. Conecta el terminal negativo del zumbador a GND.

Lado de contactos del relé: cómo usar la función de corte

El módulo de relé proporciona contactos de conmutación aislados, normalmente:

  • COM: común
  • NO: normalmente abierto
  • NC: normalmente cerrado

Para un objetivo de relé de corte por fuga de agua, la configuración práctica más adecuada para principiantes es:

  • Colocar el relé en serie con la línea de control de baja tensión del dispositivo que deseas detener.
  • Elegir COM + NC si quieres el circuito conectado durante el funcionamiento normal en seco y desconectado cuando se active la alarma de fuga.
  • Elegir COM + NO si quieres el circuito desconectado durante el funcionamiento normal en seco y conectado solo durante la alarma. Esto es menos común para corte.

Disposición de contactos recomendada para principiantes

Para un controlador de corte simple:

  • Cable externo de fuente/control de baja tensión -> COM
  • Relé NC -> entrada de carga/control

Resultado:

  • Estado seco: COM y NC están conectados, por lo que se permite el circuito de carga/control.
  • Estado de fuga: el relé conmuta alejándose de NC, abriendo la ruta de control y creando una acción de corte.

Nota importante de configuración sobre la lógica del relé

Algunos módulos de relé son activos en LOW:

  • escribir LOW en la entrada enciende el relé (ON)
  • escribir HIGH apaga el relé (OFF)

Otros son activos en HIGH.
El sketch de abajo admite esto usando una constante de configuración para que no tengas que reescribir la lógica.

Código validado

Sketch completo de Arduino: water_leak_shutoff_relay.ino

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

/*
  water_leak_shutoff_relay.ino

  Device model:
  Arduino UNO R3 (ATmega328P) + water leak sensor + 1-channel relay module + piezo buzzer

  Goal:
  Detect water using analog input, confirm the leak with simple timing/filter logic,
  trigger an audible alarm, and switch a relay for shutoff control.

  Serial output:
  9600 baud
*/

const int SENSOR_PIN = A0;
const int RELAY_PIN = 8;
const int BUZZER_PIN = 9;

// Relay module behavior:
// true  = relay input is active LOW
// false = relay input is active HIGH
const bool RELAY_ACTIVE_LOW = true;

// Sensor thresholds:
// Lower analog readings often mean "more water" on many common modules,
// but some modules behave differently. Validate with Serial Monitor first.
const int LEAK_THRESHOLD_WET = 500;   // Reading at or below this means "wet"
const int LEAK_THRESHOLD_DRY = 560;   // Reading at or above this means "dry again"

// Timing filters to avoid false triggers from noise or tiny splashes
const unsigned long WET_CONFIRM_MS = 400;
const unsigned long DRY_CONFIRM_MS = 1500;
const unsigned long STATUS_PRINT_MS = 500;

// Buzzer pattern timing
const unsigned long BUZZER_ON_MS = 180;
const unsigned long BUZZER_OFF_MS = 220;

bool leakAlarm = false;

unsigned long wetStartMs = 0;
unsigned long dryStartMs = 0;
unsigned long lastStatusPrintMs = 0;
unsigned long buzzerToggleMs = 0;
bool buzzerState = false;

void setRelayAlarmState(bool alarmActive) {
  // For shutoff wiring using COM + NC:
  // alarmActive should cause the relay to switch away from NC.
  bool relayEnergized = alarmActive;

  if (RELAY_ACTIVE_LOW) {
    digitalWrite(RELAY_PIN, relayEnergized ? LOW : HIGH);
  } else {
    digitalWrite(RELAY_PIN, relayEnergized ? HIGH : LOW);
  }
}

void setBuzzer(bool on) {
  if (on) {
    tone(BUZZER_PIN, 2400);  // 2.4 kHz tone
  } else {
    noTone(BUZZER_PIN);
  }
}

void printState(const char* stateName, int sensorValue) {
  Serial.print("State: ");
  Serial.print(stateName);
  Serial.print(" | sensor=");
  Serial.print(sensorValue);
  Serial.print(" | relay_alarm=");
  Serial.println(leakAlarm ? "ON" : "OFF");
}
// ...

/*
  water_leak_shutoff_relay.ino

  Device model:
  Arduino UNO R3 (ATmega328P) + water leak sensor + 1-channel relay module + piezo buzzer

  Goal:
  Detect water using analog input, confirm the leak with simple timing/filter logic,
  trigger an audible alarm, and switch a relay for shutoff control.

  Serial output:
  9600 baud
*/

const int SENSOR_PIN = A0;
const int RELAY_PIN = 8;
const int BUZZER_PIN = 9;

// Relay module behavior:
// true  = relay input is active LOW
// false = relay input is active HIGH
const bool RELAY_ACTIVE_LOW = true;

// Sensor thresholds:
// Lower analog readings often mean "more water" on many common modules,
// but some modules behave differently. Validate with Serial Monitor first.
const int LEAK_THRESHOLD_WET = 500;   // Reading at or below this means "wet"
const int LEAK_THRESHOLD_DRY = 560;   // Reading at or above this means "dry again"

// Timing filters to avoid false triggers from noise or tiny splashes
const unsigned long WET_CONFIRM_MS = 400;
const unsigned long DRY_CONFIRM_MS = 1500;
const unsigned long STATUS_PRINT_MS = 500;

// Buzzer pattern timing
const unsigned long BUZZER_ON_MS = 180;
const unsigned long BUZZER_OFF_MS = 220;

bool leakAlarm = false;

unsigned long wetStartMs = 0;
unsigned long dryStartMs = 0;
unsigned long lastStatusPrintMs = 0;
unsigned long buzzerToggleMs = 0;
bool buzzerState = false;

void setRelayAlarmState(bool alarmActive) {
  // For shutoff wiring using COM + NC:
  // alarmActive should cause the relay to switch away from NC.
  bool relayEnergized = alarmActive;

  if (RELAY_ACTIVE_LOW) {
    digitalWrite(RELAY_PIN, relayEnergized ? LOW : HIGH);
  } else {
    digitalWrite(RELAY_PIN, relayEnergized ? HIGH : LOW);
  }
}

void setBuzzer(bool on) {
  if (on) {
    tone(BUZZER_PIN, 2400);  // 2.4 kHz tone
  } else {
    noTone(BUZZER_PIN);
  }
}

void printState(const char* stateName, int sensorValue) {
  Serial.print("State: ");
  Serial.print(stateName);
  Serial.print(" | sensor=");
  Serial.print(sensorValue);
  Serial.print(" | relay_alarm=");
  Serial.println(leakAlarm ? "ON" : "OFF");
}

void setup() {
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  // Default safe startup for this tutorial:
  // no alarm until a confirmed leak is detected.
  leakAlarm = false;
  setRelayAlarmState(leakAlarm);
  setBuzzer(false);

  Serial.begin(9600);
  delay(300);
  Serial.println("Water leak shutoff relay controller starting...");
  Serial.println("Using analog leak sensing on A0");
  Serial.println("Check dry and wet readings before final installation.");

  // Initialize timing references
  wetStartMs = 0;
  dryStartMs = 0;
  lastStatusPrintMs = 0;
  buzzerToggleMs = 0;
  buzzerState = false;
}

void loop() {
  unsigned long now = millis();
  int sensorValue = analogRead(SENSOR_PIN);

  bool wetCandidate = (sensorValue <= LEAK_THRESHOLD_WET);
  bool dryCandidate = (sensorValue >= LEAK_THRESHOLD_DRY);

  // Leak detection state machine with hysteresis and timing confirmation
  if (!leakAlarm) {
    if (wetCandidate) {
      if (wetStartMs == 0) {
        wetStartMs = now;
      }
      if (now - wetStartMs >= WET_CONFIRM_MS) {
        leakAlarm = true;
        dryStartMs = 0;
        setRelayAlarmState(leakAlarm);
        Serial.println("LEAK DETECTED: relay shutoff activated");
        printState("LEAK", sensorValue);
      }
    } else {
      wetStartMs = 0;
    }
  } else {
    if (dryCandidate) {
      if (dryStartMs == 0) {
        dryStartMs = now;
      }
      if (now - dryStartMs >= DRY_CONFIRM_MS) {
        leakAlarm = false;
        wetStartMs = 0;
        setRelayAlarmState(leakAlarm);
        setBuzzer(false);
        buzzerState = false;
        Serial.println("RESET TO DRY: relay returned to normal");
        printState("DRY", sensorValue);
      }
    } else {
      dryStartMs = 0;
    }
  }

  // Buzzer pattern during active alarm
  if (leakAlarm) {
    if (now - buzzerToggleMs >= (buzzerState ? BUZZER_ON_MS : BUZZER_OFF_MS)) {
      buzzerState = !buzzerState;
      setBuzzer(buzzerState);
      buzzerToggleMs = now;
    }
  } else {
    if (buzzerState) {
      buzzerState = false;
      setBuzzer(false);
    }
  }

  // Periodic status prints
  if (now - lastStatusPrintMs >= STATUS_PRINT_MS) {
    printState(leakAlarm ? "LEAK" : "DRY", sensorValue);
    lastStatusPrintMs = now;
  }

  delay(20);
}

Cómo calibrar el umbral si tu sensor se comporta de forma diferente

Muchas placas económicas de sensor de fuga cambian el valor analógico cuando están mojadas, pero la dirección y el rango pueden variar.
Antes del uso final:

  1. Carga el sketch tal como está.
  2. Abre el monitor serie.
  3. Anota las lecturas analógicas:
  4. completamente seco
  5. ligeramente húmedo
  6. claramente mojado
  7. Ajusta:
  8. LEAK_THRESHOLD_WET
  9. LEAK_THRESHOLD_DRY

Usa una separación entre ellos para crear histéresis, lo que ayuda a evitar conmutaciones rápidas.

Ejemplo de edición de umbral si tu sensor está seco cerca de 850 y mojado cerca de 300:

const int LEAK_THRESHOLD_WET = 450;
const int LEAK_THRESHOLD_DRY = 700;

Comandos de compilación/carga/ejecución

Tabla de comandos

Tarea Comando
Actualizar índice de placas arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno ./water_leak_shutoff_relay
Cargar sketch arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ./water_leak_shutoff_relay
Lista opcional de placas arduino-cli board list
Monitor serie opcional arduino-cli monitor --port <PORT> --config 9600

Flujo de trabajo

  1. Crea una carpeta de proyecto llamada water_leak_shutoff_relay.
  2. Guarda el sketch como:
  3. water_leak_shutoff_relay/water_leak_shutoff_relay.ino
  4. Ejecuta los comandos requeridos:
arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli compile --fqbn arduino:avr:uno ./water_leak_shutoff_relay
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ./water_leak_shutoff_relay
  1. Busca tu puerto serie si es necesario:
  2. Ejemplo Linux: /dev/ttyACM0
  3. Ejemplo Windows: COM4
  4. Ejemplo macOS: /dev/cu.usbmodem14101
  5. Abre un monitor serie a 9600 baudios para observar lecturas y cambios de estado.

Validación paso a paso

Usa estos puntos de control en orden. Cada punto de control incluye la observación esperada y la condición de aprobación.

1. Verificación de encendido y estado inactivo

  • Acción:
  • Alimenta el Arduino por USB.
  • Mantén el sensor seco y sin tocar metal ni agua.
  • Observación esperada:
  • El monitor serie imprime texto de inicio.
  • Las líneas de estado repetidas muestran State: DRY.
  • El zumbador permanece en silencio.
  • El relé permanece en su estado normal sin alarma.
  • Condición de aprobación:
  • No ocurre ninguna alarma mientras el sensor permanece seco durante al menos 30 segundos.

2. Captura de línea base de lectura en seco

  • Acción:
  • Observa los valores sensor= impresos durante 10 a 20 segundos.
  • Anota el rango seco típico.
  • Observación esperada:
  • Los valores del sensor se mantienen bastante consistentes con pequeñas fluctuaciones.
  • Condición de aprobación:
  • Las lecturas en seco permanecen claramente en un lado de LEAK_THRESHOLD_DRY.
  • Si no, debes recalibrar los umbrales antes de continuar.

3. Prueba controlada de activación por humedad

  • Acción:
  • Pon unas gotas de agua en el área de detección, o tócala con una toalla de papel húmeda.
  • Observación esperada:
  • El valor del sensor cruza el umbral de mojado.
  • Después de aproximadamente WET_CONFIRM_MS, el monitor serie imprime LEAK DETECTED.
  • El zumbador empieza a pulsar.
  • El relé cambia de estado.
  • Condición de aprobación:
  • La alarma no es instantánea por ruido, pero sí se activa de forma fiable cuando el sensor está realmente mojado.

4. Verificación del comportamiento del relé de corte

  • Acción:
  • Conecta un circuito de prueba seguro de baja tensión a través de los contactos del relé, preferiblemente usando COM + NC para funcionamiento normal con paso y apertura en alarma.
  • Repite la prueba de activación por humedad.
  • Observación esperada:
  • En estado seco, el circuito externo de baja tensión está completo.
  • En estado de fuga, el circuito se interrumpe.
  • Condición de aprobación:
  • Los contactos del relé realizan la función de corte prevista de forma consistente durante al menos 5 ciclos de prueba.

5. Verificación de recuperación en seco y anti-rebote

  • Acción:
  • Retira el agua y seca bien el sensor.
  • Observa si el sistema se restablece solo después de una sequedad estable.
  • Observación esperada:
  • El zumbador continúa hasta que el sensor vuelve por encima de LEAK_THRESHOLD_DRY durante aproximadamente DRY_CONFIRM_MS.
  • Entonces el serie imprime RESET TO DRY.
  • El relé vuelve a su estado normal.
  • Condición de aprobación:
  • No hay clics rápidos del relé ni conmutaciones falsas repetidas durante la transición de secado.

Resolución de problemas

Síntoma Causa probable Solución
El relé parece invertido: el corte ocurre en estado seco La polaridad de entrada del relé o la elección de contactos es la opuesta a la esperada Cambia RELAY_ACTIVE_LOW, o mueve la carga de NO a NC según corresponda
El sensor siempre indica mojado Umbral demasiado alto, sensor contaminado o agua ya puenteando las pistas Limpia y seca el sensor, inspecciona las lecturas, baja el umbral de mojado
El sensor nunca se activa con agua Umbral demasiado bajo, pin equivocado usado o malentendido del tipo de salida del sensor Confirma que AO esté conectado a A0, observa los valores serie cuando esté mojado, ajusta los umbrales
El zumbador no suena Polaridad incorrecta del zumbador, incompatibilidad entre tipo pasivo/activo o error de cableado Vuelve a comprobar D9 y GND, prueba el zumbador por separado, asegúrate de usar un zumbador compatible con tone()
El relé vibra cerca del umbral Valores ruidosos del sensor o histéresis insuficiente Aumenta la separación entre los umbrales de mojado y seco; aumenta los tiempos de confirmación
La carga falla Puerto incorrecto o falta el núcleo AVR Ejecuta arduino-cli board list, verifica <PORT>, vuelve a ejecutar la instalación del núcleo
La salida serie es ilegible Velocidad en baudios incorrecta Configura el monitor a 9600 baudios
El módulo de relé reinicia el Arduino o se comporta de forma extraña Problema de consumo/ruido o cableado deficiente Mantén el cableado corto, asegúrate de tener conexiones sólidas de 5 V/GND, prueba los módulos individualmente

Mejoras

Fiabilidad y calidad de instalación

  • Añade una bandeja para sensor o monta el sensor en el punto más bajo donde el agua se acumularía primero.
  • Usa un segundo sensor de fuga en lógica en paralelo para cobertura de dos zonas, como uno bajo un fregadero y otro detrás de un electrodoméstico.
  • Añade un modo de alarma enclavada para que, una vez detectada una fuga, se requiera un reinicio manual incluso después del secado.
  • Coloca el Arduino y el relé en una pequeña carcasa, con el sensor en un cable que se extienda hasta el área supervisada.

Mejor comportamiento de control

  • Añade un botón de prueba manual para simular una fuga sin usar agua.
  • Añade un botón de silencio que silencie el zumbador pero mantenga el relé en estado de corte.
  • Registra conteos de eventos o marcas de tiempo por serie para que un estudiante pueda documentar ciclos de prueba repetidos.

Integración más segura en el mundo real

  • Usa el relé para controlar solo una válvula de baja tensión o interfaz de control, no cableado desconocido de alta potencia.
  • Sustituye el zumbador piezoeléctrico por un zumbador montado en panel para una alerta audible más clara.
  • Si más adelante se añade una válvula de corte real, elige una válvula que tenga un comportamiento a prueba de fallos claro y una tensión de control compatible.

Lista de verificación final

  • [ ] Usé Arduino UNO R3 (ATmega328P) + sensor de fuga de agua + módulo de relé de 1 canal + zumbador piezoeléctrico.
  • [ ] El sensor está cableado a A0, la entrada del relé a D8, el zumbador a D9.
  • [ ] Conecté todas las tierras juntas.
  • [ ] Guardé el sketch como water_leak_shutoff_relay.ino.
  • [ ] Ejecuté:
  • [ ] arduino-cli core update-index
  • [ ] arduino-cli core install arduino:avr
  • [ ] arduino-cli compile --fqbn arduino:avr:uno ./water_leak_shutoff_relay
  • [ ] arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ./water_leak_shutoff_relay
  • [ ] Verifiqué las lecturas serie en estado seco.
  • [ ] Calibré LEAK_THRESHOLD_WET y LEAK_THRESHOLD_DRY si fue necesario.
  • [ ] Probé la detección de fuga con una pequeña cantidad de agua controlada.
  • [ ] Confirmé que el patrón del zumbador se activa durante una fuga.
  • [ ] Confirmé que el relé interrumpe la ruta de control de baja tensión prevista durante una fuga.
  • [ ] Sequé el sensor y verifiqué una recuperación limpia al estado normal.
  • [ ] Entiendo que esto es un prototipo educativo, no un sistema de seguridad certificado.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué placa microcontroladora se utiliza en el proyecto descrito?




Pregunta 2: ¿Qué sucede cuando el sistema detecta agua?




Pregunta 3: ¿Cuál es el tiempo aproximado de conmutación del relé tras detectar agua?




Pregunta 4: ¿Qué tipo de alarma local incluye el prototipo?




Pregunta 5: ¿Para qué caso de uso se menciona la protección bajo fregadero?




Pregunta 6: ¿Dónde se recomienda colocar el sensor en el caso de la lavadora?




Pregunta 7: ¿Qué aprende el usuario en el prototipo educativo de automatización?




Pregunta 8: ¿Qué módulo de relé se menciona en la descripción del proyecto?




Pregunta 9: ¿En qué ubicación se sugiere detectar fugas tempranas para una respuesta rápida de corte?




Pregunta 10: ¿Qué tipo de circuito se busca cortar cuando se detecta agua?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:


Caso práctico: protector 12 V con Arduino UNO

Caso práctico: protector 12 V con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un controlador de desconexión de carga por batería baja de 12 V usando un Arduino UNO R3, divisor de voltaje, módulo de relé de 1 canal y LCD HD44780 de 16×2. Toma muestras continuas del voltaje de la batería, muestra la lectura en vivo en el LCD y corta la alimentación de la carga cuando la batería cae por debajo de un umbral seguro como 11.8 V, luego la reconecta solo después de la recuperación con histéresis.

Por qué importa / Casos de uso

  • Protege una pequeña batería de plomo-ácido de 12 V en un cobertizo solar, caja de iluminación o sistema de energía portátil desconectando cargas como tiras LED, un ventilador de 5–20 W o pequeños accesorios de CC antes de que ocurra una descarga profunda dañina.
  • Evita problemas de “batería misteriosamente agotada” en proyectos de hobby que funcionan durante la noche colocando el controlador entre la batería y la carga, preservando carga utilizable para el día siguiente.
  • Proporciona un monitor educativo de batería para laboratorios y talleres donde los estudiantes puedan observar actualizaciones de voltaje en vivo en el LCD, probar puntos de corte y ver cómo la histéresis evita el golpeteo rápido del relé cerca del umbral.

Resultado esperado

  • Medición de voltaje actualizada aproximadamente 2–5 veces por segundo en el LCD, con una latencia práctica de monitoreo inferior a 500 ms.
  • Corte automático por relé cuando el voltaje de la batería cae por debajo de un límite definido, por ejemplo 11.8 V, con reconexión por encima de un umbral mayor como 12.4 V.
  • Comportamiento de conmutación estable con carga mínima sobre el Arduino UNO, típicamente muy por debajo del 10% de uso de CPU y sin uso significativo de GPU.
  • Un módulo de protección reutilizable que puede reajustarse para distintos sistemas de 12 V ajustando la calibración del divisor y los umbrales de voltaje.

Audiencia: principiantes en Arduino, estudiantes y aficionados que construyen sistemas alimentados por batería; Nivel: principiante a intermedio

Arquitectura/flujo: batería de 12 V → divisor de voltaje → entrada analógica de Arduino para sensado escalado; Arduino → LCD 16×2 para mostrar voltaje/estado en vivo; salida digital de Arduino → módulo de relé de 1 canal → desconexión/reconexión de carga según lógica de umbral e histéresis.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 4 tablas y 3 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este proyecto es un prototipo educativo de bajo voltaje, no un producto certificado de protección de batería. Sus límites deben entenderse con claridad.

  • Seguridad de la batería
  • Incluso a 12 V, las baterías pueden entregar corriente muy alta.
  • Un error de cableado puede sobrecalentar los cables, dañar la batería o causar chispas.
  • Usa un fusible en línea en el conductor positivo de la batería al pasar más allá de una demostración en protoboard.

  • Seguridad de los contactos del relé

  • El módulo de relé controla el camino de la carga, pero su capacidad nominal de contacto debe coincidir con la carga real.
  • No conmutar cargas que excedan la especificación de corriente o voltaje del módulo.
  • No asumir que el módulo de relé es adecuado para cargas inductivas, motores o cargas con altos picos sin la protección apropiada.

  • Sin voltaje de red

  • Este tutorial debe usarse solo para trabajo educativo de CC de bajo voltaje.
  • No uses el módulo de relé para conmutar la red eléctrica doméstica a menos que tengas formación formal, prácticas adecuadas de aislamiento y hardware conforme. Eso queda fuera del alcance de este tutorial básico.

  • Límites de la protoboard

  • Las protoboards son adecuadas para lógica y pruebas ligeras, pero no son ideales para caminos de batería/carga de corriente más alta.
  • Para un prototipo reutilizable real, lleva el cableado de la carga a terminales apropiados, cables más gruesos y una carcasa protegida.

  • Límites de la química de la batería

  • Los umbrales de ejemplo aquí son valores educativos por defecto para una demostración simple de 12 V.
  • Diferentes baterías requieren distintos límites de voltaje y reglas de carga/descarga.
  • Antes del uso práctico, establece umbrales adecuados para tu tipo de batería y caso de uso.

  • Límites del prototipo

  • Este proyecto es útil como herramienta de enseñanza y como protector simple de batería para hobby, pero no sustituye a un sistema de gestión de baterías diseñado profesionalmente.
  • No debe usarse para infraestructura crítica, vehículos, instalaciones desatendidas de alta potencia ni equipos críticos para la seguridad.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

batería de 12 V

divisor de voltaje

entrada analógica de Arduino para sensado…

LCD 16×2 para mostrar voltaje/estado en v…

módulo de relé de 1 canal

desconexión/reconexión de carga según lóg…

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Sketch

arduino-cli compile

Upload

Prueba funcional

Resumen conceptual de las herramientas usadas para comprobar el material publicado.

Requisitos previos

Antes de comenzar, el estudiante debe sentirse cómodo con:

  • Subir un sketch a un Arduino UNO R3
  • Usar una protoboard y cables jumper
  • Medir voltaje de CC con un multímetro
  • Entender que:
  • los pines de entrada analógica miden solo de 0 a 5 V
  • se requiere un divisor de voltaje para una batería de 12 V
  • un relé es un interruptor controlado por el Arduino, no un regulador de potencia

Conocimientos de base recomendados:

  • Ley de Ohm a nivel básico
  • Salidas digitales (HIGH / LOW)
  • Lectura analógica con analogRead()

También prepara una fuente de batería segura de bajo voltaje, como:

  • una pequeña batería sellada de plomo-ácido de 12 V,
  • o una fuente de alimentación de banco con corriente limitada ajustada de 10.5 V a 13.5 V para pruebas.

Materiales

Usa exactamente este modelo y familia de dispositivo:

  • Arduino UNO R3 (ATmega328P) + divisor de voltaje + módulo de relé de 1 canal + LCD HD44780 de 16×2

Lista de piezas sugeridas:

Elemento Especificación exacta / sugerida Propósito
Controlador principal Arduino UNO R3 (ATmega328P) Lee el voltaje, controla el relé, actualiza el LCD
Pantalla LCD compatible con HD44780 de 16×2, interfaz paralela Muestra voltaje y estado de la carga
Placa de relé Módulo de relé de 1 canal de 5 V Conecta/desconecta la carga
Resistencia del divisor R1 30 kOhm, 1/4 W, 1% preferido Resistencia superior para medición de batería
Resistencia del divisor R2 7.5 kOhm, 1/4 W, 1% preferido Resistencia inferior para medición de batería
Capacitor de filtro opcional Cerámico de 100 nF Filtrado de ruido en la entrada analógica
Lado batería/carga Batería de 12 V o fuente de banco con corriente limitada Fuente de prueba
Carga de prueba Lámpara de 12 V, ventilador pequeño, carga resistiva o segmento de tira LED Demuestra la función de desconexión
Cables Cables jumper y conductores de batería Interconexión
Protoboard Tamaño completo o medio tamaño Montaje
Cable USB USB A a B para UNO Programación y alimentación
Multímetro Multímetro digital Verificación del divisor y del voltaje de batería

¿Por qué 30 kOhm y 7.5 kOhm?

Este divisor escala el voltaje de la batería por:

[
V_{A0} = V_{BAT} \times \frac{7.5}{30 + 7.5} = V_{BAT} \times 0.2
]

Entonces:

  • una batería de 12.0 V se convierte en aproximadamente 2.4 V en A0
  • una batería de 15.0 V se convierte en aproximadamente 3.0 V en A0

Eso permanece de forma segura por debajo del límite analógico de 5 V del Arduino.


Configuración/Conexión

Esta sección explica el cableado solo con texto y tablas, como se solicitó.

1) Disposición de alimentación

Para seguridad y claridad para principiantes:

  • Alimenta el Arduino UNO desde el cable USB
  • Alimenta el módulo de relé desde los 5 V y GND del Arduino
  • Usa la batería solo para:
  • la medición mediante el divisor de voltaje
  • y el camino de carga conmutado a través de los contactos del relé

Esto mantiene simple el lado lógico durante las pruebas.

2) Cableado del divisor de voltaje

Conecta el divisor de medición de batería así:

  • Positivo de batería -> R1 (30 kOhm) -> punto medio del divisor
  • Punto medio del divisor -> R2 (7.5 kOhm) -> negativo de batería
  • Punto medio del divisor -> Arduino A0
  • Negativo de batería -> Arduino GND

Reducción de ruido opcional:

  • Coloca un capacitor de 100 nF de A0 a GND

Importante:

  • El Arduino debe compartir tierra con el negativo de la batería para medir voltaje.
  • Nunca conectes el positivo de la batería directamente a A0.

3) Cableado del módulo de relé

La mayoría de los módulos de relé de 1 canal tienen estos pines de bajo voltaje:

  • VCC
  • GND
  • IN

Conecta:

  • Relé VCC -> Arduino 5V
  • Relé GND -> Arduino GND
  • Relé IN -> pin digital 8 del Arduino

Para los contactos conmutados de la carga, la mayoría de los módulos proporcionan:

  • COM
  • NO (normalmente abierto)
  • NC (normalmente cerrado)

Usa el relé para que la carga reciba alimentación solo cuando la batería esté en buen estado:

  • Positivo de batería -> relé COM
  • Relé NO -> positivo de carga
  • Negativo de carga -> negativo de batería

Esto significa:

  • cuando el relé está activado y el sistema permite la carga, COM se conecta a NO
  • cuando ocurre el corte, la conexión se abre y la carga se apaga

4) Cableado del LCD

Este tutorial usa el modo paralelo de 4 bits con la biblioteca estándar LiquidCrystal.

Conecta los pines del LCD de la siguiente manera:

  • LCD VSS -> GND
  • LCD VDD -> 5V
  • LCD VO -> control de contraste
  • Práctica recomendada: conectar al pin central de un potenciómetro de 10 kOhm
  • Otros extremos del potenciómetro -> 5V y GND
  • LCD RS -> pin 12 del Arduino
  • LCD RW -> GND
  • LCD E -> pin 11 del Arduino
  • LCD D4 -> pin 5 del Arduino
  • LCD D5 -> pin 4 del Arduino
  • LCD D6 -> pin 3 del Arduino
  • LCD D7 -> pin 2 del Arduino
  • LCD A (retroiluminación +) -> 5V a través de una resistencia adecuada si tu módulo lo requiere
  • LCD K (retroiluminación -) -> GND

5) Resumen completo de conexiones

Función Pin del módulo Conexión Arduino / batería
Entrada de medición de batería Punto medio del divisor A0
Parte inferior del divisor Extremo inferior de R2 GND y negativo de batería
Control del relé IN D8
Alimentación del relé VCC 5V
Tierra del relé GND GND
LCD RS RS D12
LCD Enable E D11
Datos LCD D4 D5
Datos LCD D5 D4
Datos LCD D6 D3
Datos LCD D7 D2
Alimentación LCD VDD 5V
Tierra LCD VSS GND
LCD RW RW GND

6) Estrategia de umbrales

Para una demostración de batería de 12 V:

  • Umbral de desconexión: 11.80 V
  • Umbral de reconexión: 12.40 V

Esto crea histéresis. Sin histéresis, el relé podría vibrar alrededor de un solo umbral.

También es útil un breve retardo de corte. En este proyecto:

  • el voltaje debe permanecer por debajo del umbral de desconexión durante varios segundos antes de que el relé se abra

Eso ayuda a ignorar caídas temporales.


Código validado

low_battery_load_disconnect.ino

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// Pin assignments
const int PIN_BATTERY = A0;
const int PIN_RELAY = 8;

// Relay module behavior:
// Many modules are ACTIVE LOW.
// Set RELAY_ON_LEVEL and RELAY_OFF_LEVEL to match your hardware.
const int RELAY_ON_LEVEL = LOW;
const int RELAY_OFF_LEVEL = HIGH;

// Voltage divider values in ohms
const float R1 = 30000.0;  // battery+ to A0
const float R2 = 7500.0;   // A0 to GND

// ADC reference for standard UNO powered by USB
const float ADC_REF_VOLTAGE = 5.0;
const int ADC_MAX = 1023;

// Battery thresholds
const float DISCONNECT_VOLTAGE = 11.80;
const float RECONNECT_VOLTAGE  = 12.40;

// Timing
const unsigned long SAMPLE_INTERVAL_MS = 250;
const unsigned long LCD_INTERVAL_MS = 500;
const unsigned long SERIAL_INTERVAL_MS = 1000;
const unsigned long LOW_VOLTAGE_DELAY_MS = 5000;

// Averaging
const int NUM_SAMPLES = 20;

// State variables
bool loadConnected = true;
bool lowVoltagePending = false;
unsigned long lowVoltageStartMs = 0;
unsigned long lastSampleMs = 0;
unsigned long lastLcdMs = 0;
unsigned long lastSerialMs = 0;
float filteredBatteryVoltage = 0.0;

// Read battery voltage using averaging
float readBatteryVoltage() {
  long total = 0;

  for (int i = 0; i < NUM_SAMPLES; i++) {
    total += analogRead(PIN_BATTERY);
    delay(2);
  }

  float adc = total / (float)NUM_SAMPLES;
  float vA0 = adc * ADC_REF_VOLTAGE / ADC_MAX;
  float batteryVoltage = vA0 * ((R1 + R2) / R2);

  return batteryVoltage;
}

void setLoadConnected(bool enabled) {
  loadConnected = enabled;
  digitalWrite(PIN_RELAY, enabled ? RELAY_ON_LEVEL : RELAY_OFF_LEVEL);
}

void updateControlLogic(float vbat, unsigned long nowMs) {
  if (loadConnected) {
    if (vbat < DISCONNECT_VOLTAGE) {
      if (!lowVoltagePending) {
        lowVoltagePending = true;
        lowVoltageStartMs = nowMs;
      } else if ((nowMs - lowVoltageStartMs) >= LOW_VOLTAGE_DELAY_MS) {
        setLoadConnected(false);
        lowVoltagePending = false;
      }
    } else {
      lowVoltagePending = false;
    }
  } else {
    // Load is disconnected; reconnect only after voltage rises enough
    if (vbat > RECONNECT_VOLTAGE) {
      setLoadConnected(true);
      lowVoltagePending = false;
    }
// ...

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// Pin assignments
const int PIN_BATTERY = A0;
const int PIN_RELAY = 8;

// Relay module behavior:
// Many modules are ACTIVE LOW.
// Set RELAY_ON_LEVEL and RELAY_OFF_LEVEL to match your hardware.
const int RELAY_ON_LEVEL = LOW;
const int RELAY_OFF_LEVEL = HIGH;

// Voltage divider values in ohms
const float R1 = 30000.0;  // battery+ to A0
const float R2 = 7500.0;   // A0 to GND

// ADC reference for standard UNO powered by USB
const float ADC_REF_VOLTAGE = 5.0;
const int ADC_MAX = 1023;

// Battery thresholds
const float DISCONNECT_VOLTAGE = 11.80;
const float RECONNECT_VOLTAGE  = 12.40;

// Timing
const unsigned long SAMPLE_INTERVAL_MS = 250;
const unsigned long LCD_INTERVAL_MS = 500;
const unsigned long SERIAL_INTERVAL_MS = 1000;
const unsigned long LOW_VOLTAGE_DELAY_MS = 5000;

// Averaging
const int NUM_SAMPLES = 20;

// State variables
bool loadConnected = true;
bool lowVoltagePending = false;
unsigned long lowVoltageStartMs = 0;
unsigned long lastSampleMs = 0;
unsigned long lastLcdMs = 0;
unsigned long lastSerialMs = 0;
float filteredBatteryVoltage = 0.0;

// Read battery voltage using averaging
float readBatteryVoltage() {
  long total = 0;

  for (int i = 0; i < NUM_SAMPLES; i++) {
    total += analogRead(PIN_BATTERY);
    delay(2);
  }

  float adc = total / (float)NUM_SAMPLES;
  float vA0 = adc * ADC_REF_VOLTAGE / ADC_MAX;
  float batteryVoltage = vA0 * ((R1 + R2) / R2);

  return batteryVoltage;
}

void setLoadConnected(bool enabled) {
  loadConnected = enabled;
  digitalWrite(PIN_RELAY, enabled ? RELAY_ON_LEVEL : RELAY_OFF_LEVEL);
}

void updateControlLogic(float vbat, unsigned long nowMs) {
  if (loadConnected) {
    if (vbat < DISCONNECT_VOLTAGE) {
      if (!lowVoltagePending) {
        lowVoltagePending = true;
        lowVoltageStartMs = nowMs;
      } else if ((nowMs - lowVoltageStartMs) >= LOW_VOLTAGE_DELAY_MS) {
        setLoadConnected(false);
        lowVoltagePending = false;
      }
    } else {
      lowVoltagePending = false;
    }
  } else {
    // Load is disconnected; reconnect only after voltage rises enough
    if (vbat > RECONNECT_VOLTAGE) {
      setLoadConnected(true);
      lowVoltagePending = false;
    }
  }
}

void updateLcd(float vbat, unsigned long nowMs) {
  if (nowMs - lastLcdMs < LCD_INTERVAL_MS) {
    return;
  }
  lastLcdMs = nowMs;

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Bat:");
  lcd.print(vbat, 2);
  lcd.print("V");

  lcd.setCursor(0, 1);

  if (loadConnected) {
    if (lowVoltagePending) {
      unsigned long elapsed = nowMs - lowVoltageStartMs;
      unsigned long remain = 0;
      if (elapsed < LOW_VOLTAGE_DELAY_MS) {
        remain = (LOW_VOLTAGE_DELAY_MS - elapsed + 999) / 1000;
      }
      lcd.print("LOW WAIT ");
      lcd.print(remain);
      lcd.print("s");
    } else {
      lcd.print("LOAD ON");
    }
  } else {
    lcd.print("CUT OFF");
  }
}

void printSerialStatus(float vbat, unsigned long nowMs) {
  if (nowMs - lastSerialMs < SERIAL_INTERVAL_MS) {
    return;
  }
  lastSerialMs = nowMs;

  Serial.print("Vbat=");
  Serial.print(vbat, 3);
  Serial.print(" V, Load=");
  Serial.print(loadConnected ? "ON" : "OFF");
  Serial.print(", Pending=");
  Serial.println(lowVoltagePending ? "YES" : "NO");
}

void setup() {
  pinMode(PIN_RELAY, OUTPUT);
  setLoadConnected(true);

  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Battery Guard");
  lcd.setCursor(0, 1);
  lcd.print("Starting...");

  Serial.begin(9600);
  delay(1500);

  filteredBatteryVoltage = readBatteryVoltage();
}

void loop() {
  unsigned long nowMs = millis();

  if (nowMs - lastSampleMs >= SAMPLE_INTERVAL_MS) {
    lastSampleMs = nowMs;

    float rawV = readBatteryVoltage();

    // Light smoothing filter
    filteredBatteryVoltage = 0.8 * filteredBatteryVoltage + 0.2 * rawV;

    updateControlLogic(filteredBatteryVoltage, nowMs);
    updateLcd(filteredBatteryVoltage, nowMs);
    printSerialStatus(filteredBatteryVoltage, nowMs);
  }
}

Notas sobre el sketch

  • El código asume un módulo de relé activo en bajo común, donde llevar IN a nivel bajo activa el relé.
  • Si tu relé se comporta de la manera opuesta:
  • cambia:
    • const int RELAY_ON_LEVEL = LOW;
    • const int RELAY_OFF_LEVEL = HIGH;
  • por:
    • const int RELAY_ON_LEVEL = HIGH;
    • const int RELAY_OFF_LEVEL = LOW;

Salida de referencia opcional del monitor serie

Vbat=12.681 V, Load=ON, Pending=NO
Vbat=12.503 V, Load=ON, Pending=NO
Vbat=11.752 V, Load=ON, Pending=YES
Vbat=11.741 V, Load=ON, Pending=YES
Vbat=11.730 V, Load=OFF, Pending=NO
Vbat=12.452 V, Load=ON, Pending=NO

Comandos de compilación/carga/ejecución

Usa Arduino CLI exactamente como se requiere.

Tabla de comandos

Tarea Comando
Actualizar índice de placas arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno low_battery_load_disconnect
Cargar sketch arduino-cli upload --fqbn arduino:avr:uno --port <PORT> low_battery_load_disconnect

Ejemplo de sesión de terminal

arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli compile --fqbn arduino:avr:uno low_battery_load_disconnect
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> low_battery_load_disconnect

Flujo de trabajo corto

  1. Crea una carpeta llamada low_battery_load_disconnect.
  2. Guarda el sketch como low_battery_load_disconnect.ino dentro de esa carpeta.
  3. Conecta el Arduino UNO R3 por USB.
  4. Ejecuta el comando de compilación.
  5. Reemplaza <PORT> por tu puerto serie real:
  6. Ejemplo en Linux: /dev/ttyACM0
  7. Ejemplo en Windows: COM4
  8. Ejemplo en macOS: /dev/cu.usbmodem14101
  9. Carga el sketch.
  10. Abre un monitor serie a 9600 baud si quieres diagnósticos adicionales.

Validación paso a paso

El objetivo aquí es validar el proyecto como un prototipo práctico de desconexión de carga por batería baja, no solo verificar que el código se cargue.

1) Encendido y verificación de pantalla

Acción
– Conecta el Arduino por USB.
– Alimenta el lado de medición de batería y conecta la carga de prueba a través del camino del relé.
– Ajusta el potenciómetro de contraste del LCD hasta que el texto sea legible.

Observación esperada
– El LCD primero muestra texto de inicio como Battery Guard.
– Luego muestra una lectura de voltaje en la línea 1.
– La línea 2 muestra LOAD ON, LOW WAIT o CUT OFF.

Condición de aprobación
– El LCD es legible y se actualiza automáticamente.
– No hay bloques aleatorios ni pantalla en blanco después de ajustar el contraste.


2) Verificación del divisor y de la precisión de medición

Acción
– Mide el voltaje real de la batería con un multímetro directamente en los terminales de la batería.
– Compáralo con el voltaje mostrado en el LCD y, opcionalmente, en el monitor serie.

Observación esperada
– El voltaje en LCD/serie es cercano a la lectura del multímetro.
– Pequeñas diferencias son normales porque:
– los 5 V del USB pueden no ser exactamente 5.000 V
– las tolerancias de las resistencias afectan el escalado
– las lecturas del ADC tienen algo de ruido

Condición de aprobación
– La lectura sigue correctamente los cambios de voltaje y es razonablemente cercana al multímetro.
– Si está desviada de forma consistente, la calibración puede mejorarse después ajustando ADC_REF_VOLTAGE o los valores de las resistencias en el código.


3) Prueba del relé en funcionamiento normal

Acción
– Ajusta la batería o fuente de banco a un nivel saludable por encima de 12.40 V.
– Observa el estado del relé y el comportamiento de la carga de prueba.

Observación esperada
– El relé está energizado en el estado “carga habilitada”.
– El LCD muestra LOAD ON.
– La carga recibe alimentación de batería a través de COM a NO del relé.

Condición de aprobación
– La carga se enciende y permanece encendida de forma estable por encima del umbral de reconexión.


4) Prueba de espera por bajo voltaje y corte temporizado

Acción
– Baja lentamente la alimentación de la batería por debajo de 11.80 V.
– Mantenla por debajo del umbral durante más de 5 segundos.

Observación esperada
– El LCD cambia a LOW WAIT con una cuenta regresiva o una breve indicación del tiempo restante.
– Después del retardo, el relé cambia de estado y la carga se desconecta.
– Luego el LCD muestra CUT OFF.

Condición de aprobación
– El relé no se dispara inmediatamente ante una caída breve.
– El relé sí desconecta después de que el voltaje permanezca bajo durante todo el retardo.


5) Prueba de histéresis y recuperación

Acción
– Después del corte, vuelve a elevar la alimentación.
– Primero prueba un valor entre 11.80 V y 12.40 V, luego súbelo por encima de 12.40 V.

Observación esperada
– Entre los umbrales, el relé permanece apagado.
– Una vez que el voltaje supera el umbral de reconexión, el relé reconecta y el LCD vuelve a LOAD ON.

Condición de aprobación
– El relé no vibra cerca de un solo umbral.
– La reconexión ocurre solo por encima del umbral más alto.


Solución de problemas

Síntoma Causa probable Solución
El LCD enciende pero no se ve texto Pin de contraste mal ajustado Usa un potenciómetro de 10 kOhm en VO del LCD y ajústalo lentamente
El LCD muestra caracteres aleatorios Cableado incorrecto de RS/E/D4-D7 Revisa de nuevo el mapeo de pines del LCD según el tutorial
El voltaje mostrado es demasiado alto o demasiado bajo Valores del divisor incorrectos o falta tierra común Verifica los valores de las resistencias con un multímetro y conecta el negativo de batería a GND del Arduino
La lectura analógica salta mucho Cableado ruidoso o punto de medición flotante Mantén los cables cortos, agrega 100 nF de A0 a GND y asegúrate de tener una tierra sólida
El relé nunca conmuta Lógica de entrada del relé opuesta a la del código Intercambia RELAY_ON_LEVEL y RELAY_OFF_LEVEL
El relé hace clic pero la carga sigue apagada Se usó el terminal de contacto incorrecto Usa COM y NO para el comportamiento normal de encendido cuando está saludable
La carga nunca se desconecta Umbral demasiado bajo o calibración de medición incorrecta Imprime valores por serie, compáralos con el multímetro, ajusta umbrales o ADC_REF_VOLTAGE
El Arduino se reinicia cuando conmuta el relé Perturbación de alimentación o problema de cableado Mantén ordenado el cableado lógico, alimenta el UNO por USB estable, evita alimentar cargas grandes desde los 5 V del Arduino
El voltaje de batería en el LCD es cero o casi cero A0 no está conectado al punto medio del divisor Revisa el cable de A0 y la continuidad del punto medio del divisor

Mejoras

Mejor calidad de medición

  • Reemplaza las suposiciones de referencia USB de 5 V por:
  • una constante de calibración medida,
  • o un método de referencia más avanzado si tu lección más adelante cubre calibración de ADC.
  • Usa resistencias del 1% para el divisor.
  • Agrega una ventana de promediado mayor o filtrado por mediana si tu fuente de batería es ruidosa.

Comportamiento de protección de batería más robusto

  • Agrega un zumbador para advertencia de batería baja antes del corte.
  • Agrega un botón de reinicio manual para que el usuario deba reconocer la desconexión antes de reconectar.
  • Guarda el último evento de corte en EEPROM para un seguimiento simple del uso.

Mejor carcasa y usabilidad en campo

  • Monta el Arduino, el relé y el LCD en una pequeña caja de proyecto.
  • Agrega terminales de tornillo para:
  • entrada de batería,
  • salida de carga,
  • y cableado con fusible.
  • Coloca etiquetas en:
  • BAT +
  • BAT -
  • LOAD +
  • LOAD -

Una mejora muy práctica para estudiantes es convertir esto en un módulo portátil de protección de batería para una caja de iluminación de camping, un pequeño demostrador solar o un paquete educativo de batería para alarma.


Lista de verificación final

  • [ ] Usé Arduino UNO R3 (ATmega328P).
  • [ ] Usé un divisor de voltaje y no conecté el voltaje de la batería directamente a A0.
  • [ ] Usé un módulo de relé de 1 canal con tierra compartida con el Arduino.
  • [ ] Cableé el LCD HD44780 de 16×2 en modo de 4 bits como se indicó.
  • [ ] Instalé el núcleo AVR de Arduino con Arduino CLI.
  • [ ] El sketch compiló con:
  • arduino-cli compile --fqbn arduino:avr:uno low_battery_load_disconnect
  • [ ] El sketch se cargó con:
  • arduino-cli upload --fqbn arduino:avr:uno --port <PORT> low_battery_load_disconnect
  • [ ] El LCD muestra el voltaje de batería en vivo.
  • [ ] El relé mantiene la carga encendida por encima del umbral de reconexión.
  • [ ] El relé desconecta la carga después de que el voltaje permanezca por debajo del umbral de desconexión durante el período de retardo.
  • [ ] La carga se reconecta solo después de que el voltaje sube por encima del umbral más alto.
  • [ ] Comparé la lectura con un multímetro.
  • [ ] Entiendo que esto es un prototipo educativo de bajo voltaje, no un dispositivo de protección certificado.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es el objetivo principal del sistema descrito?




Pregunta 2: ¿Qué placa se menciona para construir el controlador?




Pregunta 3: ¿Qué componente se usa para mostrar la lectura de voltaje en vivo?




Pregunta 4: ¿Qué sucede cuando el voltaje de la batería cae por debajo de un umbral seguro como 11.8 V?




Pregunta 5: ¿Para qué sirve la histéresis en este proyecto?




Pregunta 6: ¿Qué componente se menciona junto con el Arduino y el LCD para medir el voltaje de la batería?




Pregunta 7: ¿Qué tipo de batería se busca proteger en uno de los casos de uso?




Pregunta 8: ¿Cuál de las siguientes cargas se menciona como ejemplo de uso?




Pregunta 9: ¿Dónde podría usarse este controlador según el texto?




Pregunta 10: ¿Qué valor de umbral seguro de desconexión se menciona como ejemplo?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme: