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

PiezaFunción en el proyectoNotas
Arduino UNO R3 (ATmega328P)Controlador principalEjecuta la lógica de conteo y actualiza la pantalla
Par de sensores IR de barrera APrimera posición del hazDetecta un lado del cruce
Par de sensores IR de barrera BSegunda posición del hazDetecta el segundo lado del cruce
Pantalla TM1637 de 4 dígitosPantalla local de ocupaciónMuestra conteo de 0 a 9999
Protoboard y jumpersInterconexión de prototipadoManté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]);
    }
// ...

🔒 Este contenido es premium. Con la suscripción mensual (7 días gratis) podrás desbloquear el material didáctico completo y el pack PDF listo para imprimir.🔓 Desbloquéalo — 7 días gratis
/*
  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

TareaComando
Actualizar índice de placasarduino-cli core update-index
Instalar núcleo AVRarduino-cli core install arduino:avr
Compilar sketcharduino-cli compile --fqbn arduino:avr:uno dual_ir_beam_visitor_counter
Cargar sketcharduino-cli upload --fqbn arduino:avr:uno --port <PORT> dual_ir_beam_visitor_counter
Abrir monitor seriearduino-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íntomaCausa probableSolución
La pantalla permanece en blancoTM1637 VCC/GND invertidos, pines CLK/DIO incorrectos, jumper defectuosoRevisa de nuevo la alimentación y confirma CLK -> D4, DIO -> D5
La pantalla enciende pero nunca cambia de 0Salidas de sensores no conectadas, sensores desalineados, lógica de sensor incorrectaConfirma 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 entrarLas etiquetas físicas de los haces A y B están invertidasIntercambia las posiciones físicas de los haces o intercambia las conexiones D2 y D3
Un cruce produce dos conteosHaces demasiado cercanos, reflejos, persona demorándose, enfriamiento insuficienteAumenta 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 cruceInterferencia IR ambiental, cables flojos, módulos de sensores inestablesAléjalo de la luz solar directa, ajusta conexiones, mejora el montaje
El monitor serie muestra tiempos de espera con frecuenciaHaces demasiado separados o movimiento demasiado lentoReduce la separación entre haces o aumenta ligeramente SEQUENCE_TIMEOUT_MS
El conteo nunca disminuye por debajo de 0 pero las salidas parecen ignoradasEl conteo inicial ya es ceroRealiza primero algunas entradas y luego prueba salidas
La carga fallaPuerto incorrecto o núcleo Arduino ausenteEjecuta 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:
Scroll al inicio