Caso práctico: Monitor de buzón IoT con ESP32

Caso práctico: Monitor de buzón IoT con ESP32 — ESP32 con reed switch, imán y módulo LiPo para detectar apertura de buzón

Objetivo y caso de uso

Qué vas a construir: Un monitor de buzón IoT alimentado por batería y altamente eficiente que permanece en un estado de suspensión profunda de micropotencia, se despierta instantáneamente cuando la puerta del buzón se abre, transmite una alerta por Wi-Fi a través de una solicitud HTTP GET y vuelve a suspenderse inmediatamente.

Por qué es importante / Casos de uso

  • Elimina viajes innecesarios: Los usuarios ya no necesitan revisar físicamente un buzón vacío en condiciones climáticas adversas o a través de entradas largas.
  • Maximiza el ciclo de vida de la batería: Al utilizar las capacidades de suspensión profunda del ESP32 (consumiendo ~10µA), el dispositivo minimiza los ciclos de trabajo activos, demostrando habilidades cruciales de gestión de energía.
  • Arquitectura orientada a eventos: Enseña la transición del sondeo continuo a los despertares impulsados por interrupciones de hardware, un concepto fundamental en las redes de sensores comerciales.
  • Seguridad y registro de acceso: La misma arquitectura puede reutilizarse para monitorear armarios restringidos, cajas fuertes o puertas perimetrales sin requerir energía cableada.

Resultado esperado

  • El ESP32 entra en un estado de suspensión profunda consumiendo una corriente mínima (menos de 15µA).
  • Abrir la puerta activa un despertar por hardware a través del GPIO del RTC (Reloj de Tiempo Real) en menos de 500 milisegundos.
  • El dispositivo se conecta exitosamente al Wi-Fi, transmite el payload HTTP y se apaga en menos de 3 segundos para preservar la vida útil de la batería.

Audiencia: Desarrolladores de IoT, Ingenieros de Sistemas Embebidos; Nivel: Intermedio

Arquitectura/flujo: Interruptor Reed (Interrupción de hardware) → Despertar RTC del ESP32 (latencia <500ms) → Conexión Wi-Fi → Solicitud HTTP GET → Suspensión profunda (Deep Sleep)

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. Para este perfil ESP32 DevKitC, el proyecto se comprobó como proyecto PlatformIO: el validador extrajo platformio.ini y src/main.cpp, creó un proyecto temporal y ejecutó pio run contra platform = espressif32, board = esp32dev y framework = arduino. También revisó la estructura del artículo, que los comandos sean copiables con guiones ASCII, y que no aparezcan stacks no soportados como ESP-IDF directo o placas ESP32 no acotadas.

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 PlatformIO config + 1 ESP32 source/pio run.
  • 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 código publicado, pero no sustituye la prueba física sobre tu placa ESP32 DevKitC exacta, tu cableado, alimentación y entorno WiFi local.

Nota educativa de seguridad

Advertencia sobre los límites del prototipo: Este proyecto está destinado estrictamente como un prototipo educativo.
* Seguridad de la batería: Este proyecto involucra baterías de polímero de litio (LiPo). El mal manejo, la sobrecarga o el cortocircuito de las baterías LiPo pueden provocar incendios o explosiones. Utiliza siempre un controlador de carga dedicado (como el TP4056 con circuito de protección integrado) y nunca conectes una celda LiPo directamente al ESP32 sin una regulación de voltaje adecuada. No dejes circuitos de baterías experimentales desatendidos mientras se cargan. Asegúrate de que tu módulo de batería incluya protección contra sobredescarga para evitar daños a la celda durante despliegues prolongados.

Diagrama de bloques conceptual

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

Arquitectura funcional

Botón local

ESP32 BLE

Paquete advertising

LED de estado

Móvil escáner

Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

Resumen conceptual de las herramientas usadas para comprobar el proyecto ESP32 publicado.

Requisitos previos

Antes de comenzar este tutorial, asegúrate de tener listo lo siguiente:
* Una computadora con Windows, macOS o Linux.
* Visual Studio Code (VSCode) instalado.
* La extensión PlatformIO IDE instalada en VSCode.
* Familiaridad básica con la sintaxis de C++ y circuitos electrónicos (redes pull-up/pull-down).
* Una red Wi-Fi de 2.4 GHz (ESP32 no soporta Wi-Fi de 5 GHz).
* Un servidor HTTP local o un endpoint de webhook para recibir la alerta.

Materiales

Para este proyecto, utiliza la siguiente configuración exacta de hardware:
* ESP32 DevKitC: La placa del microcontrolador principal que incluye el módulo ESP-WROOM-32.
* Interruptor Reed: Un interruptor de contacto magnético (normalmente abierto). Cuando el imán está cerca, el interruptor se cierra. Cuando se retira el imán (se abre la puerta), el interruptor se abre.
* Módulo de batería LiPo: Una batería de polímero de litio de 3.7V emparejada con un módulo de carga/elevación de 5V (por ejemplo, un módulo de power bank basado en TP4056) para suministrar 5V de manera segura al pin 5V (o VIN) del ESP32 DevKitC.
* Componentes pasivos:
* Una resistencia de 10kΩ (usada como resistencia pull-down para el interruptor Reed).
* Accesorios: Protoboard, cables puente (jumpers) y un cable de datos micro-USB.

Configuración/Conexión

Notas de configuración de hardware

El ESP32 DevKitC requiere controladores específicos para comunicarse por USB. Dependiendo de la revisión exacta de tu placa, utilizará un puente USB a UART CP210x o CH34x. Si tu sistema operativo o PlatformIO no reconocen tu placa, descarga e instala los controladores oficiales CP210x de Silicon Labs o CH340 de WCH.

Lógica del circuito

El núcleo de este proyecto es la interrupción de hardware. El procesador principal del ESP32 se apaga durante la suspensión profunda. Solo el coprocesador de ultra bajo consumo (ULP) y la memoria/periféricos del RTC permanecen activos. Usaremos la fuente de despertar ext0, que permite que un GPIO del RTC despierte al procesador principal cuando se detecta un nivel lógico específico.

Usaremos el GPIO 33 (un pin compatible con RTC) para el interruptor Reed.
* Buzón cerrado: El imán está cerca del interruptor Reed. El interruptor está cerrado, conectando el GPIO 33 a 3.3V. El ESP32 ve una señal HIGH.
* Buzón abierto: La puerta se abre, el imán se aleja y el interruptor se abre. La resistencia pull-down de 10kΩ fuerza el GPIO 33 a LOW.
* Condición de despertar: Configuraremos el ESP32 para que se despierte cuando el GPIO 33 cambie a LOW.

Tabla de cableado

Componente Pin/Terminal Pin del ESP32 DevKitC Notas
Módulo de batería LiPo Salida 5V / VOUT 5V (o VIN) Proporciona energía cuando el USB está desconectado.
Módulo de batería LiPo GND / Tierra GND Tierra común.
Interruptor Reed Terminal 1 3V3 Proporciona 3.3V cuando el interruptor está cerrado.
Interruptor Reed Terminal 2 GPIO 33 Pin de señal para el despertar RTC.
Resistencia de 10kΩ Pata 1 GPIO 33 Configuración pull-down.
Resistencia de 10kΩ Pata 2 GND Configuración pull-down.

Nota: Desconecta la línea de 5V del módulo de batería LiPo del ESP32 cuando conectes el ESP32 a tu computadora por USB para evitar conflictos de energía.

Firmware de la aplicación

El siguiente código maneja la transición a la suspensión profunda, la conexión Wi-Fi, la solicitud HTTP y la lógica de despertar por hardware.

platformio.ini

Crea un nuevo proyecto de PlatformIO para el ESP32 DevKitC y reemplaza el contenido de platformio.ini con la siguiente configuración:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 921600

src/main.cpp

Reemplaza el contenido de src/main.cpp con el siguiente código. Actualiza las variables de red y del webhook para que coincidan con tu entorno antes de la implementación.

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

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// ==========================================
// Configuration
// ==========================================
const char* WIFI_SSID     = "IoT_Network";
const char* WIFI_PASSWORD = "SecurePassword123";
const char* WEBHOOK_URL   = "http://192.168.1.100/mailbox-alert";

// Pin Definitions
const gpio_num_t REED_SWITCH_PIN = GPIO_NUM_33; // Must be an RTC GPIO for ext0 wakeup

// Timeouts
const int WIFI_TIMEOUT_MS = 10000; // 10 seconds max to connect

// ==========================================
// Helper Functions
// ==========================================

void printWakeupReason() {
    esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
    switch(wakeup_reason) {
        case ESP_SLEEP_WAKEUP_EXT0:     
            Serial.println("Wakeup caused by external signal using RTC_IO (Door Opened!)"); 
            break;
        case ESP_SLEEP_WAKEUP_TIMER:    
            Serial.println("Wakeup caused by timer"); 
            break;
        default:                        
            Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); 
            break;
    }
}

void sendAlert() {
    Serial.print("Connecting to Wi-Fi: ");
    Serial.println(WIFI_SSID);

    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    unsigned long startAttemptTime = millis();

    // Wait for connection with a timeout to prevent draining battery
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS) {
        Serial.print(".");
        delay(500);
    }

    Serial.println();

    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("Failed to connect to Wi-Fi. Going back to sleep.");
        return; // Abort and let the main loop put the device to sleep
    }
// ...

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// ==========================================
// Configuration
// ==========================================
const char* WIFI_SSID     = "IoT_Network";
const char* WIFI_PASSWORD = "SecurePassword123";
const char* WEBHOOK_URL   = "http://192.168.1.100/mailbox-alert";

// Pin Definitions
const gpio_num_t REED_SWITCH_PIN = GPIO_NUM_33; // Must be an RTC GPIO for ext0 wakeup

// Timeouts
const int WIFI_TIMEOUT_MS = 10000; // 10 seconds max to connect

// ==========================================
// Helper Functions
// ==========================================

void printWakeupReason() {
    esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
    switch(wakeup_reason) {
        case ESP_SLEEP_WAKEUP_EXT0:     
            Serial.println("Wakeup caused by external signal using RTC_IO (Door Opened!)"); 
            break;
        case ESP_SLEEP_WAKEUP_TIMER:    
            Serial.println("Wakeup caused by timer"); 
            break;
        default:                        
            Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); 
            break;
    }
}

void sendAlert() {
    Serial.print("Connecting to Wi-Fi: ");
    Serial.println(WIFI_SSID);

    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    unsigned long startAttemptTime = millis();

    // Wait for connection with a timeout to prevent draining battery
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS) {
        Serial.print(".");
        delay(500);
    }

    Serial.println();

    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("Failed to connect to Wi-Fi. Going back to sleep.");
        return; // Abort and let the main loop put the device to sleep
    }

    Serial.println("Wi-Fi connected!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    // Send HTTP GET request
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        Serial.print("Sending alert to: ");
        Serial.println(WEBHOOK_URL);

        http.begin(WEBHOOK_URL);
        int httpResponseCode = http.GET();

        if (httpResponseCode > 0) {
            Serial.print("HTTP Response code: ");
            Serial.println(httpResponseCode);
        } else {
            Serial.print("Error code: ");
            Serial.println(httpResponseCode);
        }
        http.end();
    }

    // Disconnect Wi-Fi to save power before sleeping
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
}

// ==========================================
// Main Setup and Loop
// ==========================================

void setup() {
    Serial.begin(115200);
    delay(1000); // Allow serial monitor to catch up

    Serial.println("\n--- Mailbox Monitor Booting ---");

    // Determine why the ESP32 woke up
    esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
    printWakeupReason();

    // If woke up because the door opened (EXT0)
    if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) {
        sendAlert();
    } else {
        // Normal boot (e.g., first power on or reset)
        Serial.println("Initial boot. System is armed and ready.");
    }

    // Configure Deep Sleep Wakeup
    // Door open = magnet away = switch open = pull-down resistor pulls to LOW (0).
    // Wake up when GPIO 33 goes LOW (0).
    esp_sleep_enable_ext0_wakeup(REED_SWITCH_PIN, 0); 

    Serial.println("Entering deep sleep now. Waiting for door to open...");
    Serial.flush(); 

    // Enter Deep Sleep
    esp_deep_sleep_start();
}

void loop() {
    // The loop is intentionally empty.
    // The ESP32 goes to sleep in setup() and resets entirely upon waking.
}

Comandos de Compilación/Flasheo/Ejecución

Para desplegar el código en tu ESP32 DevKitC usando PlatformIO, utiliza la terminal integrada en VSCode.

Referencia de comandos

Acción Comando Propósito
Compilar código pio run Compila el código C++ y las bibliotecas sin subirlo.
Subir firmware pio run --target upload Flashea el binario compilado al ESP32 por USB.
Monitor serie pio device monitor Abre la terminal para ver las salidas de Serial.print.

Flujo de trabajo

  1. Conecta el ESP32 DevKitC a tu computadora usando un cable micro-USB con capacidad de transferencia de datos.
  2. Abre la terminal de VSCode en el directorio de tu proyecto.
  3. Compila y sube el firmware ejecutando:
    pio run --target upload
  4. Abre inmediatamente el monitor serie para observar la secuencia de arranque:
    pio device monitor

Validación paso a paso

Sigue estos puntos de control agrupados para asegurarte de que tu prototipo funcione correctamente.

  1. Arranque inicial y armado
    • Acción: Presiona el botón EN (Reinicio) en el ESP32.
    • Observación esperada: El monitor serie imprime «Initial boot. System is armed and ready.» Luego, el monitor imprime «Entering deep sleep now.»
    • Condición de éxito: El dispositivo entra exitosamente en suspensión profunda sin volver a despertarse inmediatamente.
  2. Simulando buzón cerrado
    • Acción: Coloca el imán directamente junto al interruptor Reed.
    • Observación esperada: No sucede nada. El ESP32 permanece en suspensión profunda.
    • Condición de éxito: El sistema ignora el estado cerrado (el GPIO 33 está en HIGH).
  3. Activando el despertar (Buzón abierto)
    • Acción: Aleja rápidamente el imán del interruptor Reed.
    • Observación esperada: El monitor serie imprime «Wakeup caused by external signal using RTC_IO (Door Opened!)».
    • Condición de éxito: La interrupción de hardware despierta exitosamente al ESP32 de la suspensión profunda.
  4. Conexión de red y transmisión
    • Acción: Espera de 2 a 5 segundos mientras observas el monitor serie.
    • Observación esperada: El monitor imprime «Wi-Fi connected!», seguido de la dirección IP y luego «HTTP Response code: 200».
    • Condición de éxito: El endpoint del webhook registra la solicitud GET entrante.
  5. Retorno a la suspensión
    • Acción: Observa las salidas finales del monitor serie.
    • Observación esperada: El monitor imprime «Entering deep sleep now. Waiting for door to open…»
    • Condición de éxito: El ESP32 interrumpe la conexión Wi-Fi y apaga su procesador principal, listo para el siguiente evento.

Solución de problemas

Si el sistema no se comporta como se espera, consulta la siguiente tabla.

Síntoma Causa probable Solución
El código no se sube (Tiempo de espera agotado) El ESP32 no está entrando en modo bootloader. Mantén presionado el botón BOOT en el DevKitC mientras ejecutas el comando de subida, y suéltalo cuando comience la conexión.
Bucles constantes de despertar La lógica del interruptor Reed está flotante. Verifica que la resistencia pull-down de 10kΩ esté conectada de forma segura entre el GPIO 33 y GND. Asegúrate de que el imán esté lo suficientemente cerca para cerrar el interruptor inicialmente.
La conexión Wi-Fi falla / Tiempo de espera agotado Credenciales incorrectas o red de 5GHz. Verifica WIFI_SSID y WIFI_PASSWORD. Asegúrate de que tu enrutador esté transmitiendo una red de 2.4GHz.
Detector de caída de voltaje (Brownout) activado Energía insuficiente durante la transmisión Wi-Fi. El puerto USB o el módulo LiPo no pueden suministrar el pico de ~300mA requerido para el Wi-Fi. Intenta usar un cable USB de mejor calidad o asegúrate de que la batería LiPo esté completamente cargada.
Sin salida en el monitor serie Discrepancia en los baudios. Asegúrate de que monitor_speed = 115200 esté configurado en platformio.ini y coincida con Serial.begin(115200) en el código.

Mejoras

Una vez que se haya validado el prototipo básico, considera estas mejoras para un dispositivo listo para producción:

  • Optimización de energía:
    • IP estática: Asigna una dirección IP estática al ESP32 en lugar de usar DHCP. Esto puede reducir el tiempo de conexión Wi-Fi de ~3 segundos a menos de 500ms, ahorrando drásticamente la vida útil de la batería.
    • Reemplazo del LDO de hardware: Las placas ESP32 DevKitC estándar utilizan un regulador de voltaje AMS1117 con una alta corriente de reposo (~1.5mA). Para un verdadero funcionamiento con micropotencia, haz la transición a una placa personalizada o a un regulador de baja caída (LDO) como el HT7333.
  • Conectividad y confiabilidad:
    • Implementación de MQTT: Reemplaza la solicitud HTTP GET con una publicación MQTT a través de la biblioteca PubSubClient. MQTT tiene menor sobrecarga y es más adecuado para ecosistemas IoT.
    • Monitoreo del voltaje de la batería: Conecta la salida de la batería LiPo a un pin ADC del ESP32 (a través de un divisor de voltaje) para transmitir el estado de la batería junto con la alerta de puerta abierta.

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 proyecto descrito en el artículo?




Pregunta 2: ¿Qué método utiliza el dispositivo para enviar la alerta cuando se abre el buzón?




Pregunta 3: ¿Cuál es el consumo de corriente aproximado del ESP32 durante el estado de suspensión profunda según el texto?




Pregunta 4: ¿Qué concepto fundamental de redes de sensores comerciales enseña este proyecto?




Pregunta 5: Además de buzones, ¿qué otro caso de uso de seguridad se menciona para esta arquitectura?




Pregunta 6: ¿Cuál es el límite máximo de consumo de corriente esperado cuando el ESP32 entra en estado de suspensión profunda?




Pregunta 7: ¿En cuánto tiempo se activa el despertar por hardware cuando se abre la puerta?




Pregunta 8: ¿A través de qué componente se activa el despertar por hardware del ESP32?




Pregunta 9: ¿Cuánto tiempo tarda el dispositivo en conectarse al Wi-Fi, transmitir el payload y apagarse?




Pregunta 10: ¿A qué nivel de audiencia está dirigido este artículo?




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 de lavadora con ESP32

Caso práctico: Monitor de lavadora con ESP32 — hero

Objetivo y caso de uso

Qué construirás: Un monitor no invasivo para lavadoras utilizando un ESP32 y un sensor de vibración SW-420 que detecta cuándo termina un ciclo de lavado. Utiliza lógica de tiempo de espera basada en flancos (edge-based) para activar una alarma local con zumbador/LED y una alerta HTTP por WiFi con latencia de subsegundos.

Por qué es importante / Casos de uso

  • Evita olvidar la ropa: Proporciona notificaciones inmediatas cuando la máquina se detiene, evitando el moho y la necesidad de volver a lavar.
  • Optimiza instalaciones compartidas: Reduce los tiempos de espera y los viajes innecesarios a la lavandería en residencias estudiantiles o edificios de apartamentos.
  • Monitorización no invasiva: Se adapta de forma segura a electrodomésticos antiguos «no inteligentes» al depender de vibraciones externas en lugar de circuitos de alto voltaje.
  • Lógica de máquina de estados basada en flancos: Enseña lógica de antirrebote (debouncing) y de tiempo de espera (timeout) para manejar las pausas normales de los electrodomésticos (ej. ventanas de inactividad de 3-5 minutos entre ciclos de lavado, enjuague y centrifugado).

Resultado esperado

  • Un prototipo funcional con ESP32 que detecta de manera fiable los estados de vibración y filtra las paradas falsas.
  • Alerta audiovisual local mediante un zumbador piezoeléctrico y un LED de estado.
  • Una notificación HTTP activada por WiFi entregada en < 500ms cuando la máquina completa oficialmente su ciclo.

Audiencia: Aficionados al IoT, estudiantes de electrónica y entusiastas de la domótica; Nivel: Principiante a Intermedio

Arquitectura/flujo: Sensor SW-420 → ESP32 (Máquina de estados con antirrebote y tiempo de espera) → Piezo/LED local y alerta HTTP POST por WiFi

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. Para este perfil ESP32 DevKitC, el proyecto se comprobó como proyecto PlatformIO: el validador extrajo platformio.ini y src/main.cpp, creó un proyecto temporal y ejecutó pio run contra platform = espressif32, board = esp32dev y framework = arduino. También revisó la estructura del artículo, que los comandos sean copiables con guiones ASCII, y que no aparezcan stacks no soportados como ESP-IDF directo o placas ESP32 no acotadas.

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 PlatformIO config + 1 ESP32 source/pio run.
  • 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 código publicado, pero no sustituye la prueba física sobre tu placa ESP32 DevKitC exacta, tu cableado, alimentación y entorno WiFi local.

Nota educativa de seguridad

Este proyecto es un prototipo educativo de bajo voltaje, no un producto certificado. Antes de encender el montaje, verifica el esquema de pines (pinout) de tu placa ESP32 DevKitC exacta, mantén las señales de los GPIO dentro de los límites de 3.3 V, nunca apliques 5 V a las entradas del ESP32, desconecta la alimentación antes de cambiar el cableado y utiliza fuentes externas adecuadas para relés, motores o cargas, compartiendo GND solo cuando el esquema lo requiera.

Diagrama de bloques conceptual

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

Arquitectura funcional

Sonda de agua

GPIO/ADC ESP32

Lógica de umbral

LED/buzzer

Alerta Wi-Fi

Flujo conceptual: detección de humedad, decisión local y aviso al usuario.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

Resumen conceptual de las herramientas usadas para comprobar el proyecto ESP32 publicado.

Requisitos previos

Antes de comenzar este proyecto, asegúrate de tener listo lo siguiente:
* Entorno de software: Visual Studio Code (VS Code) con la extensión PlatformIO IDE instalada.
* Conocimientos básicos de C++: Familiaridad con variables, sentencias if/else y la función millis() para retardos no bloqueantes.
* Acceso a la red: Una red WiFi de 2.4GHz con credenciales de SSID y contraseña conocidas.
* Controladores USB: Los controladores USB a UART CP210x o CH34x instalados en tu computadora (dependiendo de tu variante específica de ESP32 DevKitC) para permitir la comunicación serial y el flasheo.


Materiales

Necesitarás los siguientes componentes exactos para este montaje:
* ESP32 DevKitC: El microcontrolador central, que proporciona tanto la lógica GPIO como capacidades WiFi de 2.4GHz integradas.
* Sensor de vibración SW-420: Un módulo de vibración digital que cuenta con un comparador de voltaje LM393 y un potenciómetro integrado para ajustar el umbral.
* Zumbador piezoeléctrico (Piezo buzzer): Un módulo de zumbador piezoeléctrico activo (suena continuamente cuando recibe una señal digital HIGH).
* LED de estado: Un LED estándar de 5mm (de cualquier color, ej. azul o verde) emparejado con una resistencia limitadora de corriente de 220Ω.
* Suministros de prototipado: Una placa de pruebas (breadboard) estándar, varios cables puente (jumper) macho-macho y macho-hembra, y un cable micro-USB para programación y alimentación.


Configuración/Conexión

El sensor SW-420 emite una señal digital: permanece en LOW cuando está quieto, y emite pulsos HIGH cuando la vibración supera el umbral establecido por su potenciómetro integrado. El ESP32 leerá este pin digital.

Realiza las conexiones de acuerdo con el siguiente mapeo. Asegúrate de que tu ESP32 esté desconectado de la alimentación USB mientras realizas el cableado.

Componente Pin del componente Pin del ESP32 DevKitC Notas
Sensor SW-420 VCC 3V3 Alimenta el comparador LM393.
Sensor SW-420 GND GND Tierra común.
Sensor SW-420 DO (Salida Digital) GPIO 13 Envía pulsos HIGH durante la vibración.
Zumbador piezoeléctrico VCC / + GPIO 14 Se pone en HIGH para hacer sonar la alarma.
Zumbador piezoeléctrico GND / – GND Tierra común.
LED de estado Ánodo (Pata larga) GPIO 27 Conectar mediante una resistencia de 220Ω.
LED de estado Cátodo (Pata corta) GND Tierra común.

Nota sobre la corriente del zumbador: Los zumbadores piezoeléctricos activos estándar consumen alrededor de 10-30mA, lo cual es seguro para manejar directamente desde un pin GPIO del ESP32 (máx. 40mA por pin). Si estás utilizando una sirena más grande o un zumbador de alta potencia, debes usar un transistor de conmutación (como un 2N2222) o un módulo de relé.


Código validado

El siguiente código implementa una máquina de estados no bloqueante. Para probar la lógica rápidamente, el tiempo de espera del ciclo se establece en 15 segundos. En una implementación en el mundo real, deberías aumentar esto a 3-5 minutos para tener en cuenta las pausas de remojo/vaciado en un ciclo de lavadora.

Crea un nuevo proyecto de PlatformIO para la placa esp32dev y reemplaza el contenido de los archivos generados con el código a continuación.

platformio.ini

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

src/main.cpp

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

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// ---------------------------------------------------------
// Configuration: WiFi & Webhook
// ---------------------------------------------------------
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

// For demonstration, we use httpbin.org to echo the GET request.
// In a real application, replace this with an IFTTT Webhook, 
// Home Assistant endpoint, or custom API URL.
const char* WEBHOOK_URL = "http://httpbin.org/get?laundry=finished";

// ---------------------------------------------------------
// Configuration: Hardware Pins
// ---------------------------------------------------------
const int SW420_PIN = 13;
const int BUZZER_PIN = 14;
const int LED_PIN = 27;

// ---------------------------------------------------------
// Configuration: Timing & State Machine
// ---------------------------------------------------------
// Time required without vibration to consider the cycle "finished".
// Set to 15 seconds (15000ms) for testing. 
// For real laundry machines, use 180000ms (3 minutes) or more.
const unsigned long CYCLE_TIMEOUT_MS = 15000; 

enum MachineState {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_FINISHED
};

MachineState currentState = STATE_IDLE;
unsigned long lastVibrationTime = 0;
unsigned long lastAlertToggleTime = 0;
bool alertToggleState = false;

// Function prototypes
void connectToWiFi();
void sendWiFiAlert();
void handleAlertHardware();

void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n--- Laundry Vibration WiFi Alert ---");

    // Initialize pins
    pinMode(SW420_PIN, INPUT);
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(LED_PIN, OUTPUT);

    // Ensure outputs are off initially
    digitalWrite(BUZZER_PIN, LOW);
    digitalWrite(LED_PIN, LOW);

    connectToWiFi();

    Serial.println("System initialized. Waiting for vibration...");
}

void loop() {
    unsigned long currentMillis = millis();
    int vibrationDetected = digitalRead(SW420_PIN);

    // 1. Read Sensor & Update Timers
    if (vibrationDetected == HIGH) {
        lastVibrationTime = currentMillis;

        // If we were idle or already finished, a new vibration means a cycle is starting/restarting
        if (currentState == STATE_IDLE || currentState == STATE_FINISHED) {
            currentState = STATE_RUNNING;
            Serial.println("STATUS: Machine is now RUNNING.");
            // Ensure alert hardware is turned off when returning to running
            digitalWrite(BUZZER_PIN, LOW);
            digitalWrite(LED_PIN, HIGH); // Solid LED indicates running
        }
    }
// ...

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// ---------------------------------------------------------
// Configuration: WiFi & Webhook
// ---------------------------------------------------------
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

// For demonstration, we use httpbin.org to echo the GET request.
// In a real application, replace this with an IFTTT Webhook, 
// Home Assistant endpoint, or custom API URL.
const char* WEBHOOK_URL = "http://httpbin.org/get?laundry=finished";

// ---------------------------------------------------------
// Configuration: Hardware Pins
// ---------------------------------------------------------
const int SW420_PIN = 13;
const int BUZZER_PIN = 14;
const int LED_PIN = 27;

// ---------------------------------------------------------
// Configuration: Timing & State Machine
// ---------------------------------------------------------
// Time required without vibration to consider the cycle "finished".
// Set to 15 seconds (15000ms) for testing. 
// For real laundry machines, use 180000ms (3 minutes) or more.
const unsigned long CYCLE_TIMEOUT_MS = 15000; 

enum MachineState {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_FINISHED
};

MachineState currentState = STATE_IDLE;
unsigned long lastVibrationTime = 0;
unsigned long lastAlertToggleTime = 0;
bool alertToggleState = false;

// Function prototypes
void connectToWiFi();
void sendWiFiAlert();
void handleAlertHardware();

void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n--- Laundry Vibration WiFi Alert ---");

    // Initialize pins
    pinMode(SW420_PIN, INPUT);
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(LED_PIN, OUTPUT);

    // Ensure outputs are off initially
    digitalWrite(BUZZER_PIN, LOW);
    digitalWrite(LED_PIN, LOW);

    connectToWiFi();

    Serial.println("System initialized. Waiting for vibration...");
}

void loop() {
    unsigned long currentMillis = millis();
    int vibrationDetected = digitalRead(SW420_PIN);

    // 1. Read Sensor & Update Timers
    if (vibrationDetected == HIGH) {
        lastVibrationTime = currentMillis;

        // If we were idle or already finished, a new vibration means a cycle is starting/restarting
        if (currentState == STATE_IDLE || currentState == STATE_FINISHED) {
            currentState = STATE_RUNNING;
            Serial.println("STATUS: Machine is now RUNNING.");
            // Ensure alert hardware is turned off when returning to running
            digitalWrite(BUZZER_PIN, LOW);
            digitalWrite(LED_PIN, HIGH); // Solid LED indicates running
        }
    }

    // 2. Evaluate State Machine
    if (currentState == STATE_RUNNING) {
        // Check if the timeout has elapsed since the last detected vibration
        if (currentMillis - lastVibrationTime > CYCLE_TIMEOUT_MS) {
            currentState = STATE_FINISHED;
            Serial.println("STATUS: Cycle FINISHED! Triggering alerts.");
            digitalWrite(LED_PIN, LOW); // Turn off solid LED
            sendWiFiAlert();
        }
    }

    // 3. Handle Hardware Outputs based on State
    if (currentState == STATE_FINISHED) {
        handleAlertHardware();
    } else if (currentState == STATE_IDLE) {
        // Optional: Pulse LED slowly to show system is alive but idle
        digitalWrite(LED_PIN, (currentMillis / 1000) % 2 == 0);
    }

    // Small delay to prevent tight-loop debouncing issues
    delay(50);
}

// ---------------------------------------------------------
// Helper Functions
// ---------------------------------------------------------

void connectToWiFi() {
    Serial.print("Connecting to WiFi: ");
    Serial.println(WIFI_SSID);

    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
        delay(500);
        Serial.print(".");
        attempts++;
    }

    if (WiFi.status() == WL_CONNECTED) {
        Serial.println("\nWiFi connected.");
        Serial.print("IP Address: ");
        Serial.println(WiFi.localIP());
    } else {
        Serial.println("\nFailed to connect to WiFi. Continuing offline.");
    }
}

void sendWiFiAlert() {
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        Serial.print("Sending HTTP GET to: ");
        Serial.println(WEBHOOK_URL);

        http.begin(WEBHOOK_URL);
        int httpResponseCode = http.GET();

        if (httpResponseCode > 0) {
            Serial.print("HTTP Response code: ");
            Serial.println(httpResponseCode);
        } else {
            Serial.print("Error code: ");
            Serial.println(httpResponseCode);
        }
        http.end();
    } else {
        Serial.println("WiFi disconnected. Cannot send webhook alert.");
    }
}

void handleAlertHardware() {
    // Non-blocking toggle for Buzzer and LED (beep every 500ms)
    unsigned long currentMillis = millis();
    if (currentMillis - lastAlertToggleTime >= 500) {
        lastAlertToggleTime = currentMillis;
        alertToggleState = !alertToggleState;

        digitalWrite(BUZZER_PIN, alertToggleState ? HIGH : LOW);
        digitalWrite(LED_PIN, alertToggleState ? HIGH : LOW);
    }
}


Comandos de Compilación/Flasheo/Ejecución

Utiliza la CLI de PlatformIO Core para compilar, subir y monitorizar el proyecto. Abre el terminal integrado en VS Code y ejecuta los siguientes comandos.

Comando Propósito
pio run Compila el código fuente en C++ y comprueba si hay errores de sintaxis.
pio run --target upload Flashea el firmware compilado en el ESP32 conectado a través de USB.
pio device monitor Abre el monitor serie para ver la salida de la consola del ESP32.

Flujo de trabajo de ejecución:
1. Actualiza WIFI_SSID y WIFI_PASSWORD en src/main.cpp con las credenciales reales de tu red.
2. Conecta el ESP32 DevKitC a tu computadora mediante micro-USB.
3. Ejecuta pio run para verificar que el código se compila correctamente.
4. Ejecuta pio run --target upload para flashear el dispositivo. (Si la subida no comienza, mantén presionado el botón «BOOT» en el ESP32 hasta que aparezca el porcentaje de progreso de flasheo).
5. Ejecuta pio device monitor para observar la secuencia de arranque, la conexión WiFi y los estados del sensor.


Validación paso a paso

Sigue estos puntos de control para asegurarte de que tu prototipo funciona correctamente.

  1. Arranque y conexión WiFi
    • Observación: Observa el monitor serie después de reiniciar el ESP32.
    • Condición de éxito: La consola muestra «Connecting to WiFi…», seguido de «WiFi connected.» y una dirección IP asignada.
  2. Verificación de estado inactivo (Idle)
    • Observación: Deja el sensor completamente quieto sobre tu escritorio.
    • Condición de éxito: El monitor serie dice «System initialized. Waiting for vibration…» y el LED de estado parpadea lentamente (1Hz). No suena ningún zumbador.
  3. Detección de vibración (Estado de funcionamiento)
    • Observación: Golpea suavemente el sensor SW-420 con tu dedo o golpea la mesa en la que descansa.
    • Condición de éxito: El monitor serie imprime inmediatamente «STATUS: Machine is now RUNNING.» El LED de estado se enciende de forma fija.
  4. Finalización del ciclo (Tiempo de espera y alerta)
    • Observación: Deja de golpear y espera exactamente 15 segundos (el CYCLE_TIMEOUT_MS).
    • Condición de éxito: El monitor serie imprime «STATUS: Cycle FINISHED! Triggering alerts.» El LED y el zumbador comienzan a encenderse y apagarse cada 500ms.
  5. Verificación de Webhook/Red
    • Observación: Inmediatamente después del tiempo de espera, observa la salida HTTP en el monitor serie.
    • Condición de éxito: La consola imprime «Sending HTTP GET…» seguido de «HTTP Response code: 200». Esto confirma que el ESP32 alcanzó con éxito el servidor externo.

Solución de problemas

Síntoma Causa probable Solución
Falla la conexión WiFi Credenciales incorrectas o red de 5GHz. Verifica el SSID/Contraseña. Asegúrate de que tu enrutador esté emitiendo en una banda de 2.4GHz (ESP32 no soporta 5GHz).
El sensor siempre se activa (Sin tiempo de espera) La sensibilidad del SW-420 está ajustada demasiado alta. Gira el potenciómetro azul del módulo SW-420 en sentido antihorario para reducir la sensibilidad hasta que el LED verde integrado se apague cuando esté quieto.
El sensor nunca se activa La sensibilidad del SW-420 está ajustada demasiado baja. Gira el potenciómetro en sentido horario hasta que se active fácilmente al golpear la mesa.
El zumbador no suena Polaridad invertida o pin incorrecto. Asegúrate de que la pata más larga (o la marca +) del zumbador vaya al GPIO 14, y la otra a GND.
El código de respuesta HTTP es -1 Fallo de DNS o falta de internet. Verifica que el ESP32 esté conectado a una red con acceso activo a internet, no solo a un enrutador local sin WAN.

Mejoras

Una vez que el prototipo básico esté funcionando, considera estas mejoras para una implementación permanente:

Robustez del hardware:
* Carcasa y montaje: Coloca el ESP32 en una carcasa impresa en 3D. Fija imanes de neodimio en la parte posterior de la carcasa para que pueda adherirse de forma segura al chasis metálico de la lavadora, asegurando una excelente transferencia de vibración al SW-420.
* Zumbador pasivo / Audio: Cambia el zumbador activo por un zumbador pasivo. Luego puedes usar las capacidades PWM del ESP32 (ledcWrite) para reproducir una melodía agradable (como el clásico timbre de un electrodoméstico) en lugar de un pitido áspero.

Software y lógica:
* Tiempos de espera realistas: Aumenta CYCLE_TIMEOUT_MS a 180,000 (3 minutos) para tener en cuenta las fases de remojo y vaciado donde una lavadora se queda temporalmente completamente quieta.
* Integración MQTT: Reemplaza la solicitud HTTP GET básica con un cliente MQTT (usando la biblioteca PubSubClient) para integrarlo sin problemas con Home Assistant, Node-RED u OpenHAB.
* Modo de suspensión profunda (Deep Sleep) / Bajo consumo: Si funciona con una batería LiPo, modifica el código para entrar en el modo de suspensión profunda del ESP32. Puedes usar el pin del SW-420 como un activador externo (external wake)

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 proyecto descrito?




Pregunta 2: ¿Qué microcontrolador se utiliza en la construcción de este monitor?




Pregunta 3: ¿Qué tipo de sensor se emplea para monitorizar el estado de la lavadora?




Pregunta 4: ¿Por qué se considera que este método de monitorización es 'no invasivo'?




Pregunta 5: ¿Qué tipo de lógica se utiliza para manejar las pausas normales de los electrodomésticos?




Pregunta 6: Según el texto, ¿cuál es la duración típica de las ventanas de inactividad (pausas) entre ciclos de lavado, enjuague y centrifugado?




Pregunta 7: ¿Qué componentes se utilizan para generar la alerta audiovisual local?




Pregunta 8: ¿Cómo se envía la alerta remota cuando la máquina completa oficialmente su ciclo?




Pregunta 9: ¿Cuál es la latencia de entrega esperada para la notificación HTTP?




Pregunta 10: ¿Qué problema evita la notificación inmediata al terminar el ciclo de lavado?




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: fichaje RFID en SD con Arduino UNO

Caso práctico: fichaje RFID en SD con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un prototipo de hardware independiente que lee etiquetas RFID pasivas, obtiene la hora actual de un reloj de tiempo real (RTC) de precisión y registra el evento (UID y marca de tiempo) en un archivo CSV en una tarjeta microSD.

Por qué es importante / Casos de uso

  • Seguimiento de asistencia de estudiantes: Automatiza el registro en el aula mediante toques de tarjetas de identificación, eliminando pasar lista manualmente.
  • Registro de acceso a equipos de laboratorio: Proporciona un registro de auditoría sin conexión de quién usó exactamente una máquina específica y a qué hora.
  • Reloj de control de empleados: Actúa como un sistema básico de entrada/salida para pequeños talleres donde no hay conectividad de red disponible.
  • Auditoría de seguridad: Demuestra los principios fundamentales de los sistemas de control de acceso físico registrando los intentos de entrada en una puerta o portón.

Resultado esperado

  • Inicialización exitosa de los componentes del bus SPI compartido (microSD y RFID) y los componentes I2C (RTC).
  • Salida del monitor serie mostrando los UID de las tarjetas detectadas y las marcas de tiempo en tiempo real (latencia de lectura < 100ms).
  • Creación de un archivo ATTEND.CSV en la tarjeta microSD que contenga entradas de registro separadas por comas y con el formato adecuado.

Audiencia: Desarrolladores de sistemas embebidos y makers; Nivel: Intermedio

Arquitectura/flujo: Toque de etiqueta RFID → Lector RFID SPI → Microcontrolador → Obtención de marca de tiempo RTC I2C → Adición a CSV en microSD SPI

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, 5 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 2 Arduino/arduino-cli compile.
  • 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 construye un prototipo educativo destinado al aprendizaje de la integración de hardware, el uso compartido del bus SPI y el registro de datos. No es un sistema de control de acceso seguro. Las etiquetas MIFARE Classic utilizadas comúnmente con el MFRC522 tienen vulnerabilidades criptográficas bien documentadas y

Diagrama de bloques conceptual

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

Arquitectura funcional

Toque de etiqueta RFID

Lector RFID SPI

Microcontrolador

Obtención de marca de tiempo RTC I2C

Adición a CSV en microSD SPI

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

Para completar con éxito este tutorial, necesitas:
* Comprensión básica de la programación en C++ y la estructura de un sketch de Arduino (setup() y loop()).
* Familiaridad con la comunicación serie y la lectura de las salidas del monitor serie.
* Una computadora con el arduino-cli (Interfaz de línea de comandos de Arduino) instalado y agregado al path de tu sistema.
* Una tarjeta microSD (32GB o menor) formateada en FAT32.
* Un cable USB Tipo-A a Tipo-B para programar y alimentar el Arduino UNO.

Materiales

  • Arduino UNO R3 (ATmega328P) + módulo RFID MFRC522 + módulo microSD SPI + RTC DS3231 (Hardware objetivo exacto).
  • Etiquetas o tarjetas RFID pasivas (13.56 MHz, compatibles con MFRC522, típicamente MIFARE Classic).
  • Protoboard y cables puente premium macho-macho y macho-hembra.
  • Tarjeta microSD (formateada en FAT32).

Configuración/Conexión

Este proyecto requiere un cableado cuidadoso porque dos módulos diferentes (el MFRC522 y el módulo microSD) deben compartir el único bus SPI de hardware del Arduino. El bus SPI utiliza líneas compartidas para datos (MOSI, MISO) y reloj (SCK), pero requiere pines de selección de chip (CS) únicos para cada dispositivo. El RTC DS3231 utiliza el bus I2C, que opera en pines separados.

Advertencia de distribución de energía

  • MFRC522: Debe ser alimentado con 3.3V. Aplicar 5V al pin VCC del MFRC522 lo dañará.
  • Módulo microSD: La mayoría de los módulos microSD estándar para Arduino tienen un regulador de voltaje y un cambiador de nivel integrados. Estos deben ser alimentados con 5V.
  • DS3231: Puede operar de manera segura a 5V.

Tablas de cableado

1. Módulo RFID MFRC522 (Bus SPI 1 – Lógica 3.3V)

Pin MFRC522 Pin Arduino UNO R3 Función / Nota
3.3V 3.3V CRÍTICO: No conectar a 5V
RST Pin 9 Control de reinicio
GND GND Tierra común
IRQ Sin conectar No se usa en esta implementación de sondeo
MISO Pin 12 Master In Slave Out (SPI compartido)
MOSI Pin 11 Master Out Slave In (SPI compartido)
SCK Pin 13 Reloj serie (SPI compartido)
SDA (CS) Pin 10 Selección de chip para RFID

2. Módulo de Tarjeta MicroSD (Bus SPI 2 – Lógica 5V)

Pin MicroSD Pin Arduino UNO R3 Función / Nota
VCC 5V Alimentación para el regulador integrado
GND GND Tierra común
MISO Pin 12 Master In Slave Out (SPI compartido)
MOSI Pin 11 Master Out Slave In (SPI compartido)
SCK Pin 13 Reloj serie (SPI compartido)
CS Pin 4 Selección de chip para tarjeta SD

3. Módulo RTC DS3231 (Bus I2C)

Pin DS3231 Pin Arduino UNO R3 Función / Nota
VCC 5V Alimentación
GND GND Tierra común
SDA Pin A4 Datos I2C
SCL Pin A5 Reloj I2C

Código validado

El proyecto utiliza dos archivos de código separados. El primero es un sketch de utilidad para configurar la fecha y hora actuales en tu RTC DS3231. El segundo es la aplicación principal de registro de asistencia.

Sketch de utilidad: set_rtc_time.ino

Ejecuta este sketch una vez para programar el RTC con la hora de compilación actual de tu computadora.

/*
 * Utility: Set DS3231 RTC Time
 * This sketch sets the RTC to the date & time the sketch was compiled.
 */

#include <Wire.h>
#include <RTClib.h>

RTC_DS3231 rtc;

void setup() {
  Serial.begin(9600);
  while (!Serial) { delay(10); } // Wait for serial console

  Serial.println("Initializing RTC...");

  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC. Check wiring.");
    while (1) { delay(10); } // Halt
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
  }

  // Set the RTC to the date & time this sketch was compiled
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  Serial.println("RTC time has been successfully updated!");
  Serial.print("Current time set to: ");

  DateTime now = rtc.now();
  Serial.print(now.year(), DEC);
  Serial.print('/');
  Serial.print(now.month(), DEC);
  Serial.print('/');
  Serial.print(now.day(), DEC);
  Serial.print(" ");
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.println(now.second(), DEC);

  Serial.println("You can now flash the main attendance logger sketch.");
}

void loop() {
  // Nothing to do here
}

Aplicación principal: attendance_logger.ino

Este es el firmware principal del prototipo. Inicializa el bus SPI compartido, gestiona los pines de selección de chip, sondea tarjetas RFID y añade registros a la tarjeta SD.

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

/*
 * Project: RFID Attendance SD Logger
 * Target: Arduino UNO R3 + MFRC522 + MicroSD + DS3231
 * Description: Reads RFID tag UIDs, fetches timestamp from RTC, 
 *              and logs the data to ATTEND.CSV on the SD card.
 */

#include <SPI.h>
#include <MFRC522.h>
#include <SD.h>
#include <Wire.h>
#include <RTClib.h>

// --- Pin Definitions ---
#define SD_CS_PIN    4
#define RFID_CS_PIN  10
#define RFID_RST_PIN 9

// --- Object Instantiation ---
MFRC522 mfrc522(RFID_CS_PIN, RFID_RST_PIN);
RTC_DS3231 rtc;

// --- Global Variables ---
const char* logFileName = "ATTEND.CSV";

void setup() {
  // Initialize serial communications
  Serial.begin(9600);
  while (!Serial) { delay(10); } // Wait for serial port to connect

  Serial.println(F("--- RFID Attendance Logger Initialization ---"));

  // 1. Initialize SPI Bus
  SPI.begin();

  // 2. Initialize DS3231 RTC (I2C)
  if (!rtc.begin()) {
    Serial.println(F("ERROR: Couldn't find RTC."));
    while (1) { delay(10); }
  }
  if (rtc.lostPower()) {
    Serial.println(F("WARNING: RTC lost power. Time may be inaccurate."));
  } else {
    Serial.println(F("RTC initialized successfully."));
  }

  // 3. Initialize MicroSD Card (SPI)
  // Disable RFID SPI temporarily to ensure clean SD init
  pinMode(RFID_CS_PIN, OUTPUT);
  digitalWrite(RFID_CS_PIN, HIGH); 

  Serial.print(F("Initializing SD card..."));
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println(F("ERROR: SD card initialization failed!"));
    Serial.println(F("Check formatting (FAT32), wiring, and CS pin."));
    while (1) { delay(10); } // Halt if SD fails
  }
  Serial.println(F("SD card initialized."));

  // Write CSV Header if file doesn't exist
  if (!SD.exists(logFileName)) {
    File dataFile = SD.open(logFileName, FILE_WRITE);
    if (dataFile) {
      dataFile.println(F("Timestamp,UID"));
      dataFile.close();
      Serial.println(F("Created new ATTEND.CSV with headers."));
    } else {
      Serial.println(F("ERROR: Could not create file on SD card."));
    }
// ...

/*
 * Project: RFID Attendance SD Logger
 * Target: Arduino UNO R3 + MFRC522 + MicroSD + DS3231
 * Description: Reads RFID tag UIDs, fetches timestamp from RTC, 
 *              and logs the data to ATTEND.CSV on the SD card.
 */

#include <SPI.h>
#include <MFRC522.h>
#include <SD.h>
#include <Wire.h>
#include <RTClib.h>

// --- Pin Definitions ---
#define SD_CS_PIN    4
#define RFID_CS_PIN  10
#define RFID_RST_PIN 9

// --- Object Instantiation ---
MFRC522 mfrc522(RFID_CS_PIN, RFID_RST_PIN);
RTC_DS3231 rtc;

// --- Global Variables ---
const char* logFileName = "ATTEND.CSV";

void setup() {
  // Initialize serial communications
  Serial.begin(9600);
  while (!Serial) { delay(10); } // Wait for serial port to connect

  Serial.println(F("--- RFID Attendance Logger Initialization ---"));

  // 1. Initialize SPI Bus
  SPI.begin();

  // 2. Initialize DS3231 RTC (I2C)
  if (!rtc.begin()) {
    Serial.println(F("ERROR: Couldn't find RTC."));
    while (1) { delay(10); }
  }
  if (rtc.lostPower()) {
    Serial.println(F("WARNING: RTC lost power. Time may be inaccurate."));
  } else {
    Serial.println(F("RTC initialized successfully."));
  }

  // 3. Initialize MicroSD Card (SPI)
  // Disable RFID SPI temporarily to ensure clean SD init
  pinMode(RFID_CS_PIN, OUTPUT);
  digitalWrite(RFID_CS_PIN, HIGH); 

  Serial.print(F("Initializing SD card..."));
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println(F("ERROR: SD card initialization failed!"));
    Serial.println(F("Check formatting (FAT32), wiring, and CS pin."));
    while (1) { delay(10); } // Halt if SD fails
  }
  Serial.println(F("SD card initialized."));

  // Write CSV Header if file doesn't exist
  if (!SD.exists(logFileName)) {
    File dataFile = SD.open(logFileName, FILE_WRITE);
    if (dataFile) {
      dataFile.println(F("Timestamp,UID"));
      dataFile.close();
      Serial.println(F("Created new ATTEND.CSV with headers."));
    } else {
      Serial.println(F("ERROR: Could not create file on SD card."));
    }
  }

  // 4. Initialize MFRC522 RFID (SPI)
  mfrc522.PCD_Init();
  // Optional: Increase antenna gain if tags are hard to read
  // mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max);
  Serial.println(F("MFRC522 initialized successfully."));

  Serial.println(F("--- System Ready. Waiting for RFID tags ---"));
}

void loop() {
  // Look for new RFID cards
  if (!mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  // Select one of the cards
  if (!mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  // Get Current Time from RTC
  DateTime now = rtc.now();

  // Format Timestamp: YYYY-MM-DD HH:MM:SS
  char timeBuffer[20];
  snprintf(timeBuffer, sizeof(timeBuffer), "%04d-%02d-%02d %02d:%02d:%02d",
           now.year(), now.month(), now.day(),
           now.hour(), now.minute(), now.second());

  // Format UID to Hex String
  String uidString = "";
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    if (mfrc522.uid.uidByte[i] < 0x10) {
      uidString += "0";
    }
    uidString += String(mfrc522.uid.uidByte[i], HEX);
  }
  uidString.toUpperCase();

  // Print to Serial Monitor
  Serial.print(F("Scanned: "));
  Serial.print(timeBuffer);
  Serial.print(F(" | UID: "));
  Serial.println(uidString);

  // Log to SD Card
  File dataFile = SD.open(logFileName, FILE_WRITE);
  if (dataFile) {
    dataFile.print(timeBuffer);
    dataFile.print(F(","));
    dataFile.println(uidString);
    dataFile.close();
    Serial.println(F(" -> Logged to SD card successfully."));
  } else {
    Serial.println(F(" -> ERROR: Failed to open ATTEND.CSV for writing."));
  }

  // Halt PICC to prevent reading the same card repeatedly in a single tap
  mfrc522.PICC_HaltA();
  // Stop encryption on PCD
  mfrc522.PCD_StopCrypto1();

  // Brief delay to prevent rapid-fire multiple reads
  delay(1000); 
}

Comandos de compilación/flasheo/ejecución

Utiliza arduino-cli para compilar y subir tu código.

Tabla de referencia de comandos

Tarea Comando
Actualizar índice de core arduino-cli core update-index
Instalar core AVR arduino-cli core install arduino:avr
Instalar dependencias arduino-cli lib install "MFRC522" "RTClib" "SD"
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno <Sketch_Folder>
Subir al UNO arduino-cli upload --fqbn arduino:avr:uno --port <PORT> <Sketch_Folder>
Monitor serie arduino-cli monitor --port <PORT> --config baudrate=9600

Flujo de trabajo numerado

  1. Encuentra tu puerto: Conecta el Arduino UNO a tu computadora. Ejecuta arduino-cli board list para identificar el <PORT> (ej., COM3 en Windows, /dev/ttyACM0 en Linux).
  2. Instala las bibliotecas: Asegúrate de que las bibliotecas de terceros requeridas estén instaladas:
    arduino-cli lib install "MFRC522" "RTClib" "SD"
  3. Configura la hora del RTC:
    • Guarda el primer bloque de código en una carpeta llamada set_rtc_time.
    • Compilar: arduino-cli compile --fqbn arduino:avr:uno set_rtc_time
    • Subir: arduino-cli upload --fqbn arduino:avr:uno --port <PORT> set_rtc_time
    • Monitor: arduino-cli monitor --port <PORT> --config baudrate=9600 para verificar que se configuró la hora.
  4. Flashea el registrador principal:
    • Guarda el segundo bloque de código en una carpeta llamada attendance_logger.
    • Compilar: arduino-cli compile --fqbn arduino:avr:uno attendance_logger
    • Subir: arduino-cli upload --fqbn arduino:avr:uno --port <PORT> attendance_logger
    • Monitor: arduino-cli monitor --port <PORT> --config baudrate=9600 para ver el sistema en vivo.

Validación paso a paso

Utiliza estos puntos de control mientras monitoreas la salida serie para asegurarte de que tu prototipo sea completamente funcional.

  1. Punto de control: Inicialización del RTC
    • Acción: Reinicia el Arduino y observa el monitor serie.
    • Observación esperada: El monitor imprime «RTC initialized successfully.»
    • Condición de éxito: El sistema no se detiene con «ERROR: Couldn’t find RTC.»
  2. Punto de control: Montaje de la tarjeta SD
    • Acción: Continúa observando el monitor serie durante el arranque.
    • Observación esperada: El monitor imprime «Initializing SD card…» seguido de «SD card initialized.»
    • Condición de éxito: El sistema monta con éxito el sistema de archivos FAT32 y no se detiene.
  3. Punto de control: Inicialización del módulo RFID
    • Acción: Observa los mensajes de arranque finales.
    • Observación esperada: El monitor imprime «MFRC522 initialized successfully.» y «— System Ready. Waiting for RFID tags —«.
    • Condición de éxito: Ambos dispositivos SPI (SD y RFID) se han inicializado sin causar conflictos en el bus.
  4. Punto de control: Escaneo y registro de etiquetas
    • Acción: Toca la antena del MFRC522 con una etiqueta RFID de 13.56 MHz.
    • Observación esperada: El monitor serie muestra «Scanned: [Fecha/Hora] | UID: [Cadena Hexadecimal]» seguido de » -> Logged to SD card successfully.»
    • Condición de éxito: El UID coincide con tu tarjeta, la marca de tiempo es precisa y la escritura en la SD reporta éxito.
  5. Punto de control: Verificación de datos CSV
    • Acción: Apaga el Arduino, retira la tarjeta microSD y léela en tu computadora.
    • Observación esperada: Existe un archivo llamado ATTEND.CSV. Al abrirlo, se revela una fila de encabezado (Timestamp,UID) y una fila por cada toque de tarjeta.
    • Condición de éxito: Los datos están correctamente separados por comas y son legibles para un humano en una aplicación de hoja de cálculo.

Solución de problemas

Síntoma Causa probable Solución
Couldn't find RTC Problema de cableado I2C. Verifica que SDA esté en A4 y SCL en A5. Asegúrate de que el módulo tenga 5V y GND.
SD card initialization failed! Formato incorrecto o pin CS. Asegúrate de que la tarjeta SD esté formateada en FAT32 (no exFAT ni NTFS). Verifica que CS esté conectado al Pin 4.
La SD falla solo cuando el RFID está conectado Conflicto MISO SPI (módulos SD económicos). Algunos módulos SD no liberan la línea MISO cuando su CS está en ALTO. Intenta alimentar el módulo SD desde una fuente de 5V separada o añade un búfer triestado a la línea MISO de la SD.
El RFID no detecta tarjetas Fuente de alimentación o cableado SPI. Asegúrate de que el VCC del MFRC522 sea 3.3V, NO 5V. Verifica MOSI (11), MISO (12), SCK (13), SDA (10) y RST (9).
La marca de tiempo se reinicia a 2000-01-01 Batería del RTC agotada. Reemplaza la batería CR2032 en el módulo DS3231 y vuelve a ejecutar el sketch set_rtc_time.

Mejoras

Una vez que el registrador básico esté funcionando, considera estas mejoras temáticas para un prototipo más robusto:

  • Mecanismos de retroalimentación para el usuario:
    • Añade un zumbador piezoeléctrico para proporcionar un «pitido» audible tras un escaneo y registro exitosos.
    • Integra un LED bicolor (verde para un registro exitoso, rojo para un error de la tarjeta SD) para que el dispositivo pueda usarse sin un monitor serie conectado.
  • Gestión e integridad de datos:
    • Implementa un sistema de rotación diaria de archivos (ej., LOG_1024.CSV para el 24 de octubre) para evitar que un solo archivo CSV se vuelva demasiado grande para analizarlo rápidamente.
    • Añade una base de datos de UID conocidos en la memoria flash del Arduino (usando PROGMEM) para rechazar tarjetas desconocidas y registrar solo usuarios autorizados.
  • Energía e implementación:
    • Añade una rutina de modo de suspensión para apagar los periféricos SPI y poner el ATmega328P a dormir cuando no haya ninguna tarjeta presente, despertando mediante una interrupción de hardware (si usas un módulo RFID diferente que soporte el despertar por IRQ).

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 prototipo de hardware descrito?




Pregunta 2: ¿Qué componente se utiliza para obtener la hora actual en el prototipo?




Pregunta 3: ¿En qué formato se registran los eventos (UID y marca de tiempo) en la tarjeta microSD?




Pregunta 4: ¿Cuál de los siguientes es un caso de uso mencionado para este proyecto?




Pregunta 5: ¿Qué ventaja ofrece el sistema para talleres pequeños según el texto?




Pregunta 6: ¿Qué tipo de etiquetas RFID lee el prototipo construido?




Pregunta 7: ¿Qué bus de comunicación comparten la tarjeta microSD y el módulo RFID en este prototipo?




Pregunta 8: ¿Qué protocolo de comunicación utiliza el componente del reloj de tiempo real (RTC)?




Pregunta 9: ¿Qué beneficio aporta este sistema en el registro de acceso a equipos de laboratorio?




Pregunta 10: Según el resultado esperado, ¿qué información se debe mostrar en la salida del monitor serie?




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: ventilación de invernadero con Arduino UNO

Caso práctico: ventilación de invernadero con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Construirás un controlador de ventilación de invernadero automatizado e independiente que ajusta dinámicamente una compuerta de ventilación física mediante un servomotor en función de la temperatura ambiente. El sistema incorpora una anulación de hardware manual y lógica de histéresis para evitar la oscilación mecánica.

Por qué es importante / Casos de uso

  • Automatización agrícola: Regula invernaderos a pequeña escala de forma autónoma, eliminando la necesidad de monitoreo humano constante para prevenir el estrés de las plantas.
  • Implementación de histéresis: Utiliza umbrales de temperatura superior e inferior distintos (por ejemplo, abrir la compuerta a 26°C, cerrar a 22°C) para estabilizar el sistema y evitar el desgaste del servo por cambios rápidos.
  • Anulaciones de seguridad: Proporciona una derivación manual basada en hardware (mediante un interruptor de fin de carrera) para control inmediato durante emergencias o mantenimiento de rutina.

Resultado esperado

  • Un sistema de bucle cerrado que acciona un servo de 0° (cerrado) a 90° (abierto) con una latencia de respuesta de <50ms cuando se cruzan los umbrales de temperatura.
  • Funcionamiento mecánico suave y sin vibraciones en los límites de temperatura gracias a la histéresis programada.
  • Respuesta mecánica inmediata al interruptor de fin de carrera, reemplazando toda la lógica automatizada del sensor con latencia casi nula.

Audiencia: Makers, estudiantes de tecnología agrícola y desarrolladores de sistemas embebidos; Nivel: Principiante a Intermedio

Arquitectura/flujo: Sensor de temperatura (Entrada analógica) ➔ Microcontrolador (Lógica de histéresis e interrupciones de hardware) ➔ Salida PWM ➔ Actuador de servomotor

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 proyecto es un prototipo educativo de bajo voltaje, no un producto certificado. Antes de encender la configuración, verifique el cableado de su Arduino UNO R3, evite cortocircuitar los 5V, GND o pines digitales, desconecte la alimentación antes de cambiar las conexiones y use módulos de interfaz adecuados para relés, motores o cargas externas.

Requisitos previos

Para completar con éxito este tutorial, debe tener:
* Una comprensión básica de cómo usar la línea de comandos/terminal en su sistema operativo.
* La CLI (Interfaz de línea de comandos) de Arduino instalada en su computadora.
* Familiaridad básica con placas de pruebas (protoboards) y conexiones de cables puente (jumpers).
* Un cable USB Tipo-B estándar para conectar el Arduino UNO a su computadora.

Materiales

Para este proyecto, debe usar EXACTAMENTE este modelo de dispositivo y lista de componentes:
* Microcontrolador: Arduino UNO R3 (ATmega328P)
* Actuador: Micro servomotor SG90
* Sensor: Sensor de temperatura analógico LM35 (encapsulado TO-92)
* Dispositivo de entrada: Interruptor de fin de carrera (microinterruptor estándar con palanca de rodillo o botón pulsador básico)
* Accesorios: 1x Placa de pruebas (protoboard) sin soldadura, varios cables puente macho a macho.

Nota: No se requieren resistencias externas para el interruptor de fin de carrera porque utilizaremos las resistencias pull-up internas del ATmega328P mediante software.

Configuración/Conexión

Un cableado adecuado es fundamental para la estabilidad de las lecturas analógicas y los movimientos del servo. El LM35 proporciona una salida de voltaje analógico lineal proporcional a la temperatura (10mV por grado Celsius). El servo SG90 se controla mediante Modulación por Ancho de Pulso (PWM), y el interruptor de fin de carrera utiliza una entrada digital simple.

Tabla de cableado

Componente Pin / Color del cable Pin del Arduino UNO R3 Función / Notas
LM35 Pin 1 (Izquierdo, cara plana hacia arriba) 5V Fuente de alimentación para el sensor de temperatura.
LM35 Pin 2 (Medio) A0 Señal de salida analógica (10mV/°C).
LM35 Pin 3 (Derecho) GND Referencia de tierra.
Servo SG90 Cable rojo 5V Fuente de alimentación para el servomotor.
Servo SG90 Cable marrón / negro GND Referencia de tierra.
Servo SG90 Cable naranja / amarillo D9 Señal PWM para controlar el ángulo del servo.
Interruptor de fin de carrera COM (Común) GND Referencia de tierra para el interruptor.
Interruptor de fin de carrera NO (Normalmente Abierto) D2 Entrada digital. En estado ALTO (HIGH) internamente; cambia a BAJO (LOW) al presionarse.

Instrucciones de conexión

  1. Distribución de energía: Conecte el pin de 5V del Arduino al riel positivo de su placa de pruebas. Conecte el pin GND del Arduino al riel negativo.
  2. Sensor LM35: Inserte el LM35 en la placa de pruebas. Con el lado plano mirándolo a usted, conecte el pin izquierdo al riel de 5V, el pin derecho al riel GND y el pin central directamente al pin analógico A0 del Arduino.
  3. Servo SG90: Conecte los cables de alimentación del servo (rojo a 5V, marrón/negro a GND) a los rieles de la placa de pruebas. Conecte el cable de señal (naranja/amarillo) al pin digital D9 del Arduino.
  4. Interruptor de fin de carrera: Conecte el terminal Común (COM) del interruptor de fin de carrera al riel GND. Conecte el terminal Normalmente Abierto (NO) al pin digital D2 del Arduino.

Código validado

La siguiente sección contiene el sketch de Arduino completo y compilable y un script de shell de Bash utilizado para automatizar el proceso de compilación y carga mediante la CLI de Arduino.

Sketch de Arduino: greenhouse_vent.ino

Cree un directorio llamado greenhouse_vent y guarde este código dentro como greenhouse_vent.ino.

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

/*
 * Greenhouse Vent Servo Controller
 * Device: Arduino UNO R3 (ATmega328P) + SG90 servo + LM35 temperature sensor + limit switch
 * 
 * Description: Reads temperature from LM35. Opens vent (servo 90 deg) if temp >= 28C.
 * Closes vent (servo 0 deg) if temp <= 25C. 
 * A limit switch on D2 acts as a manual override to force the vent open.
 */

#include <Servo.h>

// Pin Definitions
const int lm35Pin = A0;
const int limitSwitchPin = 2;
const int servoPin = 9;

// Servo Object
Servo ventServo;

// Configuration Constants
const float TEMP_OPEN_THRESHOLD = 28.0;
const float TEMP_CLOSE_THRESHOLD = 25.0;
const int ANGLE_CLOSED = 0;
const int ANGLE_OPEN = 90;

// Timing Variables for non-blocking execution
unsigned long lastUpdateMillis = 0;
const unsigned long UPDATE_INTERVAL_MS = 1000;

// State Tracking
bool ventIsOpen = false;

void setup() {
  // Initialize Serial Monitor
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for serial port to connect
  }

  // Configure Pins
  // Internal pull-up ensures the pin reads HIGH when the switch is unpressed.
  // When pressed, the switch connects the pin to GND, reading LOW.
  pinMode(limitSwitchPin, INPUT_PULLUP);

  // Attach and initialize servo to closed position
  ventServo.attach(servoPin);
  ventServo.write(ANGLE_CLOSED);

  Serial.println("========================================");
  Serial.println("Greenhouse Vent Controller Initialized");
  Serial.println("========================================");
}
// ...

/*
 * Greenhouse Vent Servo Controller
 * Device: Arduino UNO R3 (ATmega328P) + SG90 servo + LM35 temperature sensor + limit switch
 * 
 * Description: Reads temperature from LM35. Opens vent (servo 90 deg) if temp >= 28C.
 * Closes vent (servo 0 deg) if temp <= 25C. 
 * A limit switch on D2 acts as a manual override to force the vent open.
 */

#include <Servo.h>

// Pin Definitions
const int lm35Pin = A0;
const int limitSwitchPin = 2;
const int servoPin = 9;

// Servo Object
Servo ventServo;

// Configuration Constants
const float TEMP_OPEN_THRESHOLD = 28.0;
const float TEMP_CLOSE_THRESHOLD = 25.0;
const int ANGLE_CLOSED = 0;
const int ANGLE_OPEN = 90;

// Timing Variables for non-blocking execution
unsigned long lastUpdateMillis = 0;
const unsigned long UPDATE_INTERVAL_MS = 1000;

// State Tracking
bool ventIsOpen = false;

void setup() {
  // Initialize Serial Monitor
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for serial port to connect
  }

  // Configure Pins
  // Internal pull-up ensures the pin reads HIGH when the switch is unpressed.
  // When pressed, the switch connects the pin to GND, reading LOW.
  pinMode(limitSwitchPin, INPUT_PULLUP);

  // Attach and initialize servo to closed position
  ventServo.attach(servoPin);
  ventServo.write(ANGLE_CLOSED);

  Serial.println("========================================");
  Serial.println("Greenhouse Vent Controller Initialized");
  Serial.println("========================================");
}

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

  // Execute control logic at defined intervals
  if (currentMillis - lastUpdateMillis >= UPDATE_INTERVAL_MS) {
    lastUpdateMillis = currentMillis;

    // 1. Read Manual Override Limit Switch
    // LOW means the switch is pressed (override active)
    bool overrideActive = (digitalRead(limitSwitchPin) == LOW);

    // 2. Read and Calculate Temperature from LM35
    int rawADC = analogRead(lm35Pin);

    // The Arduino UNO has a 10-bit ADC (0-1023) and operates at 5.0V.
    // Voltage = (ADC Value / 1024.0) * 5.0
    float voltage = rawADC * (5.0 / 1024.0);

    // LM35 outputs 10mV per degree Celsius (0.01V/C)
    // Temperature (C) = Voltage / 0.01 = Voltage * 100.0
    float temperatureC = voltage * 100.0;

    // 3. Determine Target Vent State
    if (overrideActive) {
      // Manual override forces the vent open
      ventIsOpen = true;
    } else {
      // Temperature-based hysteresis control
      if (temperatureC >= TEMP_OPEN_THRESHOLD) {
        ventIsOpen = true;
      } else if (temperatureC <= TEMP_CLOSE_THRESHOLD) {
        ventIsOpen = false;
      }
      // If temperature is between 25.0 and 28.0, ventIsOpen remains unchanged.
    }

    // 4. Actuate Servo
    if (ventIsOpen) {
      ventServo.write(ANGLE_OPEN);
    } else {
      ventServo.write(ANGLE_CLOSED);
    }

    // 5. Log System State
    Serial.print("Temp: ");
    Serial.print(temperatureC, 1);
    Serial.print(" C | Override: ");
    Serial.print(overrideActive ? "ACTIVE " : "STANDBY");
    Serial.print(" | Vent State: ");
    Serial.println(ventIsOpen ? "OPEN  (90 deg)" : "CLOSED (0 deg)");
  }
}

Script de automatización: build_and_upload.sh

Guarde este archivo en el directorio principal de greenhouse_vent (o ajuste las rutas en consecuencia). Este script asegura que el núcleo Arduino AVR esté instalado, compila el código y lo carga en la placa.

#!/bin/bash

# Define the FQBN for Arduino UNO R3
FQBN="arduino:avr:uno"

# Define the serial port (Change this to match your system, e.g., /dev/ttyACM0 or COM3)
PORT="/dev/ttyACM0"

# Define the sketch directory
SKETCH_DIR="greenhouse_vent"

echo "Updating Arduino CLI core index..."
arduino-cli core update-index

echo "Installing Arduino AVR core..."
arduino-cli core install arduino:avr

echo "Compiling sketch..."
arduino-cli compile --fqbn $FQBN $SKETCH_DIR

if [ $? -eq 0 ]; then
    echo "Compilation successful. Uploading to $PORT..."
    arduino-cli upload --fqbn $FQBN --port $PORT $SKETCH_DIR

    if [ $? -eq 0 ]; then
        echo "Upload complete! Open serial monitor at 9600 baud."
    else
        echo "Upload failed. Please check the PORT and connection."
    fi
else
    echo "Compilation failed. Please check the source code."
fi

Comandos de compilación/flasheo/ejecución

Para desplegar el código en su Arduino UNO R3, utilizará la CLI de Arduino. A continuación se muestran los comandos exactos utilizados por el script de automatización, que también puede ejecutar manualmente.

Tabla de referencia de comandos

Acción Comando
Actualizar índice del núcleo arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno greenhouse_vent
Cargar en la placa arduino-cli upload --fqbn arduino:avr:uno --port <PORT> greenhouse_vent
Monitorizar salida arduino-cli monitor --port <PORT> --config baudrate=9600

Flujo de trabajo numerado

  1. Conecte su Arduino UNO R3 a su computadora a través del cable USB.
  2. Identifique su puerto serie. En Linux, este es típicamente /dev/ttyACM0 o /dev/ttyUSB0. En Windows, será un puerto COM como COM3. Puede encontrarlo ejecutando arduino-cli board list.
  3. Abra su terminal y navegue hasta el directorio que contiene su carpeta greenhouse_vent.
  4. Ejecute el comando de compilación: arduino-cli compile --fqbn arduino:avr:uno greenhouse_vent.
  5. Ejecute el comando de carga, reemplazando <PORT> con su puerto real: arduino-cli upload --fqbn arduino:avr:uno --port /dev/ttyACM0 greenhouse_vent.
  6. Inicie el monitor serie para ver los registros: arduino-cli monitor --port /dev/ttyACM0 --config baudrate=9600.

Validación paso a paso

Una vez que el código esté cargado y el monitor serie esté en ejecución, realice los siguientes puntos de control de validación para asegurarse de que la lógica y el hardware funcionen correctamente.

  • Punto de control 1: Inicialización y estado de reposo

    • Acción: Observe el monitor serie inmediatamente después de la carga, con la temperatura ambiente por debajo de 25°C.
    • Observación esperada: El monitor serie imprime el banner de inicialización. El servo se mueve a la posición de 0 grados. Los registros indican una temperatura por debajo de 25.0°C, Override: STANDBY y Vent State: CLOSED.
    • Condición de aprobación: El servo está físicamente en la posición cero y los registros reflejan el estado cerrado con precisión.
  • Punto de control 2: Calentamiento (Cruzando el umbral superior)

    • Acción: Pellizque suavemente el sensor LM35 entre sus dedos para elevar su temperatura. Observe el monitor serie.
    • Observación esperada: La temperatura registrada aumentará de manera constante. Una vez que alcance 28.0°C o más, el estado de ventilación (Vent State) cambiará a OPEN.
    • Condición de aprobación: El servo SG90 gira físicamente 90 grados inmediatamente cuando el registro muestra una temperatura $\ge$ 28.0°C.
  • Punto de control 3: Verificación de histéresis (Enfriamiento)

    • Acción: Suelte el LM35 y permita que se enfríe. Observe los registros a medida que la temperatura desciende entre 27.9°C y 25.1°C.
    • Observación esperada: La temperatura desciende, pero el estado de ventilación (Vent State) permanece en OPEN. El servo no se mueve.
    • Condición de aprobación: El sistema mantiene su estado actual mientras se encuentra en la banda muerta de histéresis, evitando una oscilación mecánica rápida.
  • Punto de control 4: Cruzando el umbral inferior

    • Acción: Continúe dejando que el sensor se enfríe (o sople suavemente sobre él) hasta que la temperatura descienda a 25.0°C o menos.
    • Observación esperada: El registro actualiza el estado de ventilación (Vent State) a CLOSED.
    • Condición de aprobación: El servo gira físicamente de vuelta a la posición de 0 grados.
  • Punto de control 5: Activación de anulación manual

    • Acción: Mientras la temperatura esté por debajo de 25°C (ventilación normalmente cerrada), mantenga presionado el interruptor de fin de carrera.
    • Observación esperada: El siguiente registro serie (dentro de 1 segundo) muestra Override: ACTIVE y Vent State: OPEN.
    • Condición de aprobación: El servo se mueve inmediatamente a 90 grados a pesar de la temperatura fría. Al soltar el interruptor, el servo debería volver a 0 grados en el siguiente ciclo.

Solución de problemas

Si su prototipo no se comporta como se esperaba, consulte la tabla a continuación para ver problemas comunes y sus soluciones.

Síntoma Causa probable Solución
El LM35 lee ~0°C o ~500°C constantemente Sensor cableado al revés o tierra flotante. Desconecte la alimentación inmediatamente. Compruebe la cara plana del LM35. Izquierda es 5V, Derecha es GND, Centro es A0. Vuelva a asentar los cables puente.
Las lecturas de temperatura fluctúan bruscamente (±5°C)

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é tipo de controlador construirás según el texto?




Pregunta 2: ¿Qué componente físico se utiliza para ajustar la compuerta de ventilación?




Pregunta 3: ¿Cuál es el propósito principal de implementar la lógica de histéresis en este sistema?




Pregunta 4: ¿Cómo se activa la anulación de seguridad manual (hardware override)?




Pregunta 5: Según el ejemplo del texto, ¿cómo funcionan los umbrales de temperatura con la histéresis?




Pregunta 6: ¿Cuál es el rango de movimiento esperado para el servomotor en este sistema cerrado?




Pregunta 7: ¿Cuál es la latencia de respuesta esperada del sistema de bucle cerrado?




Pregunta 8: ¿Qué problema elimina la automatización agrícola en este caso de uso?




Pregunta 9: ¿Para qué situaciones se proporciona la derivación manual basada en hardware?




Pregunta 10: ¿Qué función cumple el interruptor de fin de carrera en el 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: Nodo de relé RS485 con Arduino UNO

Caso práctico: Nodo de relé RS485 con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un nodo de relé remoto robusto y direccionable utilizando un Arduino UNO que se comunica a través de un bus RS485 de larga distancia para conmutar un relé de 5V basándose en comandos serie dirigidos.

Por qué es importante / Casos de uso

  • Automatización agrícola: Control de válvulas de riego o ventiladores de invernaderos a cientos de metros de distancia donde las señales estándar UART de 3.3V, I2C o SPI se degradarían.
  • Sistemas de alerta industrial: Activación de balizas visuales en plantas de fábrica ruidosas. La señalización diferencial RS485 proporciona una alta inmunidad a la interferencia electromagnética (EMI) de la maquinaria pesada.
  • Domótica cableada: Creación de una red cableada y determinista de interruptores distribuidos que garantiza tiempos de respuesta inferiores a 10 ms sin depender de la cobertura Wi-Fi.
  • Redes multipunto escalables: Conexión de hasta 32 nodos direccionables en un solo par trenzado de cables, asegurando que cada nodo solo reaccione a los comandos que coincidan con su ID específico.

Resultado esperado

  • El Arduino interpreta correctamente los comandos serie direccionados y conmuta el relé de 5V con una latencia cercana a cero.
  • Se mantiene una comunicación sin errores en tendidos de cable largos (hasta 1200 metros a 9600 baudios).
  • El nodo filtra correctamente el tráfico del bus, ignorando los comandos destinados a otros dispositivos en la red compartida.

Audiencia: Desarrolladores de sistemas embebidos e ingenieros de automatización; Nivel: Intermedio

Arquitectura/flujo: Controlador Maestro → Bus RS485 (Par trenzado) → Transceptor MAX485 → Arduino UNO (UART) → GPIO → Módulo de Relé de 5V

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 Python/py_compile, 1 Arduino/arduino-cli compile.
  • 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. Antes de encender la configuración, verifique el cableado de su Arduino UNO R3, evite cortocircuitar los 5 V, GND o los pines digitales, desconecte la alimentación antes de cambiar las conexiones y utilice módulos de interfaz adecuados para relés, motores o cargas externas.

Diagrama de bloques conceptual

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

Arquitectura funcional

Botón local

ESP32 BLE

Paquete advertising

LED de estado

Móvil escáner

Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.

Requisitos previos

  • Familiaridad con operaciones básicas de terminal e interfaces de línea de comandos.
  • Comprensión de la comunicación UART (Serie) estándar (Tasa de baudios, TX/RX).
  • Arduino CLI instalado y agregado al PATH de su sistema.
  • Python 3.11 instalado (para ejecutar el script maestro de validación).
  • La biblioteca pyserial instalada en su entorno de Python (pip install pyserial).

Materiales

  • Microcontrolador: Arduino UNO R3 (ATmega328P).
  • Transceptor: Módulo MAX485 RS485 (placa de conexión estándar de 8 pines con pines DI, DE, RE, RO).
  • Actuador: Módulo de relé de 5 V (optoaislado, activo en alto o activo en bajo).
  • Interfaz Maestra: Adaptador USB a RS485 (para conectar su PC al bus RS485 para pruebas).
  • Componentes pasivos: 1x resistencia de 120 ohmios (para terminación del bus, recomendada para tendidos de cable largos).
  • Cableado: Cables puente (jumper), protoboard y un cable de par trenzado para las líneas A/B de RS485.

Configuración/Conexión

RS485 es un estándar half-duplex, lo que significa que los datos viajan en ambas direcciones, pero solo en una dirección a la vez. El chip MAX485 utiliza los pines Driver Enable (DE) y Receiver Enable (RE) para alternar entre la transmisión y la recepción. Uniremos estos dos pines y los controlaremos con un solo pin digital en el Arduino.

Utilizaremos SoftwareSerial en los pines 10 y 11 para comunicarnos con el MAX485. Esto preserva el puerto serie de hardware del Arduino (pines 0 y 1) para la depuración por USB y la carga de código sin necesidad de desconectar el cableado.

Cableado del Arduino al Módulo MAX485

Pin del MAX485 Pin del Arduino UNO Función / Descripción
VCC 5V Fuente de alimentación para el transceptor.
GND GND Referencia de tierra común.
RO (Receiver Out) Pin Digital 10 Se conecta al RX de Software del Arduino.
RE (Receiver Enable) Pin Digital 2 Activo en BAJO. Unido a DE.
DE (Driver Enable) Pin Digital 2 Activo en ALTO. Unido a RE.
DI (Driver In) Pin Digital 11 Se conecta al TX de Software del Arduino.
A USB-RS485 ‘A’ Línea de bus RS485 no inversora.
B USB-RS485 ‘B’ Línea de bus RS485 inversora.

Nota: Asegúrese de que la tierra del adaptador USB a RS485 esté conectada a la tierra del Arduino para mantener un voltaje de referencia común, especialmente para pruebas de banco a corta distancia.

Cableado del Arduino al Módulo de Relé de 5V

Pin del Módulo de Relé Pin del Arduino UNO Función / Descripción
VCC / DC+ 5V Fuente de alimentación para la bobina del relé y el optoacoplador.
GND / DC- GND Referencia de tierra común.
IN / Signal Pin Digital 7 Señal de control para activar el relé.

Código validado

Las siguientes secciones contienen el código fuente completo tanto para el nodo Arduino como para el script maestro en Python utilizado para validar el bus.

Firmware del Nodo Arduino

Cree un directorio llamado rs485_relay_node y guarde el siguiente código como rs485_relay_node.ino en su interior.

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

/*
 * rs485_relay_node.ino
 * 
 * Implements a half-duplex RS485 slave node.
 * Listens for "<NODE_ID>:<COMMAND>\n".
 * Valid commands: "ON", "OFF".
 */

#include <SoftwareSerial.h>

// Pin Definitions
#define RE_DE_PIN 2     // HIGH = Transmit, LOW = Receive
#define RELAY_PIN 7     // Relay control pin
#define RX_PIN 10       // SoftwareSerial RX
#define TX_PIN 11       // SoftwareSerial TX

// Node Configuration
const String NODE_ID = "N1";
const long BAUD_RATE = 9600;

// Initialize SoftwareSerial for RS485 communication
SoftwareSerial rs485(RX_PIN, TX_PIN);

void setup() {
  // Configure RS485 control pins
  pinMode(RE_DE_PIN, OUTPUT);
  digitalWrite(RE_DE_PIN, LOW); // Default to listen mode

  // Configure Relay pin
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Default relay state (adjust if active-low)

  // Initialize Serial ports
  Serial.begin(BAUD_RATE); // Hardware serial for debugging
  rs485.begin(BAUD_RATE);  // Software serial for RS485 bus

  Serial.println("System Boot: RS485 Relay Node");
  Serial.println("Node ID: " + NODE_ID);
  Serial.println("Listening for commands...");
}

void loop() {
  // Check if data is available on the RS485 bus
  if (rs485.available()) {
    // Read the incoming frame until a newline character
    String incomingMsg = rs485.readStringUntil('\n');
    incomingMsg.trim(); // Remove carriage returns or trailing spaces

    if (incomingMsg.length() > 0) {
      Serial.println("Bus traffic detected: " + incomingMsg);
      processCommand(incomingMsg);
    }
// ...

/*
 * rs485_relay_node.ino
 * 
 * Implements a half-duplex RS485 slave node.
 * Listens for "<NODE_ID>:<COMMAND>\n".
 * Valid commands: "ON", "OFF".
 */

#include <SoftwareSerial.h>

// Pin Definitions
#define RE_DE_PIN 2     // HIGH = Transmit, LOW = Receive
#define RELAY_PIN 7     // Relay control pin
#define RX_PIN 10       // SoftwareSerial RX
#define TX_PIN 11       // SoftwareSerial TX

// Node Configuration
const String NODE_ID = "N1";
const long BAUD_RATE = 9600;

// Initialize SoftwareSerial for RS485 communication
SoftwareSerial rs485(RX_PIN, TX_PIN);

void setup() {
  // Configure RS485 control pins
  pinMode(RE_DE_PIN, OUTPUT);
  digitalWrite(RE_DE_PIN, LOW); // Default to listen mode

  // Configure Relay pin
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Default relay state (adjust if active-low)

  // Initialize Serial ports
  Serial.begin(BAUD_RATE); // Hardware serial for debugging
  rs485.begin(BAUD_RATE);  // Software serial for RS485 bus

  Serial.println("System Boot: RS485 Relay Node");
  Serial.println("Node ID: " + NODE_ID);
  Serial.println("Listening for commands...");
}

void loop() {
  // Check if data is available on the RS485 bus
  if (rs485.available()) {
    // Read the incoming frame until a newline character
    String incomingMsg = rs485.readStringUntil('\n');
    incomingMsg.trim(); // Remove carriage returns or trailing spaces

    if (incomingMsg.length() > 0) {
      Serial.println("Bus traffic detected: " + incomingMsg);
      processCommand(incomingMsg);
    }
  }
}

void processCommand(String msg) {
  // Check if the message is addressed to this specific node
  if (msg.startsWith(NODE_ID + ":")) {
    // Extract the command portion after the colon
    int separatorIndex = msg.indexOf(':');
    String action = msg.substring(separatorIndex + 1);

    if (action == "ON") {
      digitalWrite(RELAY_PIN, HIGH);
      Serial.println("Action: Relay turned ON");
      sendAcknowledgment("ACK_ON");
    } 
    else if (action == "OFF") {
      digitalWrite(RELAY_PIN, LOW);
      Serial.println("Action: Relay turned OFF");
      sendAcknowledgment("ACK_OFF");
    }
    else {
      Serial.println("Warning: Unknown command received.");
    }
  } else {
    // Message is for another node or malformed; ignore quietly
    Serial.println("Ignored: Addressed to another node.");
  }
}

void sendAcknowledgment(String ackMsg) {
  // 1. Switch MAX485 to Transmit mode
  digitalWrite(RE_DE_PIN, HIGH);

  // 2. Allow a brief moment for the transceiver to stabilize
  delay(5); 

  // 3. Transmit the acknowledgment frame
  String fullAck = NODE_ID + ":" + ackMsg;
  rs485.println(fullAck);

  // 4. Wait for the serial transmission buffer to empty completely
  // This is critical. If we pull RE_DE low too early, the transmission is cut off.
  rs485.flush(); 

  // 5. Allow the final bit to hit the wire before switching states
  delay(5);

  // 6. Switch MAX485 back to Receive mode
  digitalWrite(RE_DE_PIN, LOW);

  Serial.println("Transmitted: " + fullAck);
}

Script de Prueba Maestro en Python

Guarde el siguiente código como master_controller.py en su PC. Este script actúa como el controlador central, generando tráfico RS485 para probar el nodo Arduino.

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

#!/usr/bin/env python3
"""
master_controller.py
Validates the Arduino RS485 Relay Node by sending specific string commands
over a USB-to-RS485 adapter and waiting for acknowledgments.
"""

import serial
import time
import sys

# Windows: 'COM3', Linux: '/dev/ttyUSB0', macOS: '/dev/cu.usbserial-...'
PORT = '/dev/ttyUSB0' 
BAUD = 9600
TIMEOUT = 2.0

def send_and_wait(ser, node_id, command):
    """Sends a formatted command and waits for an acknowledgment."""
    frame = f"{node_id}:{command}\n"
    print(f"\n[Master] Sending : {frame.strip()}")

    # Send the byte-encoded string over the serial port
    ser.write(frame.encode('ascii'))

    # Wait for the node to process and respond
    time.sleep(0.1)

    # Read the response if available
    response_received = False
    start_time = time.time()

    while (time.time() - start_time) < TIMEOUT:
        if ser.in_waiting > 0:
            response = ser.readline().decode('ascii', errors='ignore').strip()
            print(f"[Master] Received: {response}")
            response_received = True
            break
        time.sleep(0.05)

    if not response_received:
        print("[Master] Error: No response received (Timeout).")
# ...

#!/usr/bin/env python3
"""
master_controller.py
Validates the Arduino RS485 Relay Node by sending specific string commands
over a USB-to-RS485 adapter and waiting for acknowledgments.
"""

import serial
import time
import sys

# Windows: 'COM3', Linux: '/dev/ttyUSB0', macOS: '/dev/cu.usbserial-...'
PORT = '/dev/ttyUSB0' 
BAUD = 9600
TIMEOUT = 2.0

def send_and_wait(ser, node_id, command):
    """Sends a formatted command and waits for an acknowledgment."""
    frame = f"{node_id}:{command}\n"
    print(f"\n[Master] Sending : {frame.strip()}")

    # Send the byte-encoded string over the serial port
    ser.write(frame.encode('ascii'))

    # Wait for the node to process and respond
    time.sleep(0.1)

    # Read the response if available
    response_received = False
    start_time = time.time()

    while (time.time() - start_time) < TIMEOUT:
        if ser.in_waiting > 0:
            response = ser.readline().decode('ascii', errors='ignore').strip()
            print(f"[Master] Received: {response}")
            response_received = True
            break
        time.sleep(0.05)

    if not response_received:
        print("[Master] Error: No response received (Timeout).")

def main():
    try:
        print(f"Opening port {PORT} at {BAUD} baud...")
        with serial.Serial(PORT, BAUD, timeout=TIMEOUT) as ser:
            time.sleep(2) # Allow port initialization

            print("--- Starting Validation Sequence ---")

            # Test 1: Turn Relay ON
            send_and_wait(ser, "N1", "ON")
            time.sleep(2)

            # Test 2: Turn Relay OFF
            send_and_wait(ser, "N1", "OFF")
            time.sleep(2)

            # Test 3: Test Address Filtering (Should be ignored by N1)
            send_and_wait(ser, "N2", "ON")

            print("\n--- Validation Sequence Complete ---")

    except serial.SerialException as e:
        print(f"Serial Port Error: {e}")
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nProcess aborted by user.")
        sys.exit(0)

if __name__ == '__main__':
    main()

Comandos de Compilación/Flasheo/Ejecución

Utilice Arduino CLI para compilar y flashear el firmware en su Arduino UNO R3. Asegúrese de que su terminal esté abierta en el directorio que contiene rs485_relay_node.

Tarea Comando
Actualizar índice del núcleo arduino-cli core update-index
Instalar núcleo AVR arduino-cli core install arduino:avr
Compilar sketch arduino-cli compile --fqbn arduino:avr:uno rs485_relay_node
Subir a la placa arduino-cli upload --fqbn arduino:avr:uno --port <PORT> rs485_relay_node

Pasos del flujo de trabajo:
1. Conecte el Arduino UNO a su PC mediante USB.
2. Identifique el puerto (por ejemplo, COM3 en Windows o /dev/ttyACM0 en Linux) utilizando arduino-cli board list.
3. Ejecute el comando de compilación para asegurarse de que no haya errores de sintaxis.
4. Ejecute el comando de subida, reemplazando <PORT> con su identificador de puerto específico.
5. Abra un monitor serie (`arduino-

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é componente principal se utiliza como microcontrolador en este proyecto?




Pregunta 2: ¿Qué protocolo de comunicación de larga distancia se utiliza en este proyecto?




Pregunta 3: ¿Qué tipo de actuador se conmuta basándose en los comandos serie?




Pregunta 4: ¿Por qué es útil el RS485 en la automatización agrícola según el texto?




Pregunta 5: ¿Qué característica del RS485 proporciona alta inmunidad a la interferencia electromagnética (EMI) en entornos industriales?




Pregunta 6: En el contexto de la domótica cableada, ¿qué ventaja ofrece este sistema frente al Wi-Fi?




Pregunta 7: ¿Cuántos nodos direccionables se pueden conectar como máximo en un solo par trenzado de cables según el texto?




Pregunta 8: ¿Qué tipo de cableado físico se menciona para conectar los nodos en la red multipunto?




Pregunta 9: ¿Cómo sabe un nodo en la red multipunto que debe reaccionar a un comando?




Pregunta 10: ¿Qué protocolos de comunicación estándar se menciona que se degradarían a cientos de metros de distancia?




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: alarma de nevera abierta con Arduino UNO

Caso práctico: alarma de nevera abierta con Arduino UNO — hero

Objetivo y caso de uso

Qué construirás: Un prototipo de monitor ambiental y sistema de alarma que rastrea el estado de la puerta del refrigerador y la temperatura interna para evitar el deterioro de los alimentos y el desperdicio de energía.

Por qué es importante / Casos de uso

  • Almacenamiento comercial y de laboratorio: Evita que las cámaras frigoríficas queden entreabiertas y garantiza que las muestras químicas sensibles a la temperatura permanezcan dentro de umbrales térmicos estrictamente definidos.
  • Conservación de energía doméstica: Elimina el desperdicio de energía y evita el quemado del compresor causado por puertas abiertas de forma prolongada en entornos residenciales.
  • Educación sobre máquinas de estado de hardware: Demuestra código de microcontrolador práctico sin bloqueo utilizando millis() para manejar múltiples sensores independientes simultáneamente sin bloqueo de subprocesos.

Resultado esperado

  • El sistema consulta continuamente el sensor de temperatura LM35 cada 2000ms (0.5 Hz) y transmite los datos al Monitor Serie para su registro.
  • Un interruptor de láminas magnético detecta con precisión los estados de puerta abierta/cerrada con una latencia cercana a cero, activando transiciones inmediatas de la máquina de estados y alarmas temporales.

Audiencia: Desarrolladores de sistemas embebidos, Educadores STEM; Nivel: Intermedio

Arquitectura/flujo: LM35 (Analógico) & Interruptor de láminas (GPIO Digital) → Microcontrolador (Máquina de estados sin bloqueo) → Registro en Monitor Serie & Disparadores de alarma

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, 1 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 proyecto es un prototipo educativo de bajo voltaje, no un producto certificado. Antes de encender la configuración, verifique el cableado de su Arduino UNO R3, evite cortocircuitar los 5 V, GND o los pines digitales, desconecte la alimentación antes de cambiar las conexiones y use módulos de interfaz adecuados para relés, motores o cargas externas.

Diagrama de bloques conceptual

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

Arquitectura funcional

LM35 (Analógico) & Interruptor de láminas…

Microcontrolador (Máquina de estados sin…

Registro en Monitor Serie & Disparadores…

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.

Método de validación y precisión

Para validar la precisión de las lecturas del sensor LM35, coloque un termómetro digital comercial calibrado exactamente en la misma ubicación que el LM35 dentro del refrigerador.
* Evidencia esperada: La salida serie del Arduino debería informar temperaturas dentro de ±0.5°C del termómetro calibrado a las temperaturas estándar del refrigerador (2.0°C a 8.0°C) después de permitir 10 minutos para que la carcasa del sensor alcance el equilibrio térmico.

Requisitos previos

Antes de comenzar este tutorial, asegúrese de tener listas las siguientes herramientas y conocimientos:
* Una computadora host con Linux, macOS o Windows.
* Arduino CLI instalado y agregado al PATH de su sistema.
* Un cable USB Tipo-A a Tipo-B para conectar el Arduino UNO a su computadora.
* Comprensión básica de cómo usar una protoboard y cables puente.

Materiales

Necesitará los componentes exactos enumerados a continuación para construir este prototipo.

Componente Modelo exacto / Especificación Cantidad Papel en el prototipo
Microcontrolador Arduino UNO R3 (ATmega328P) 1 La unidad central de procesamiento que evalúa la lógica de los sensores.
Sensor de temperatura LM35 (Paquete LM35DZ TO-92) 1 Sensor analógico de precisión que proporciona una salida lineal de 10mV/°C.
Sensor magnético Interruptor de láminas estándar (Normalmente abierto) 1 Detecta la presencia de un imán para determinar el estado de la puerta.
Imán Pequeño imán de neodimio o cerámica 1 Montado en la puerta para accionar el interruptor de láminas.
Actuador de audio Zumbador piezoeléctrico (Pasivo o Activo) 1 Emite tonos de alarma audibles.
Cableado Cables puente macho a macho ~10 Conecta los componentes en la protoboard al Arduino.
Protoboard Protoboard estándar de tamaño medio 1 Proporciona una base de creación de prototipos sin soldadura.

Nota: No requerimos una resistencia pull-up externa para el interruptor de láminas porque utilizaremos la resistencia pull-up interna del ATmega328P mediante software.

Configuración/Conexión

Siga estas instrucciones cuidadosamente. Asegúrese de que su Arduino esté desconectado de la alimentación USB mientras realiza el cableado.

1. Cableado del sensor de temperatura LM35

Sostenga el LM35 de modo que la cara plana con el texto impreso quede frente a usted y los tres pines apunten hacia abajo.
* Pin izquierdo (VCC): Conéctelo al pin de 5V en el Arduino.
* Pin central (OUT): Conéctelo al pin A0 (Analógico 0) en el Arduino.
* Pin derecho (GND): Conéctelo a un pin GND en el Arduino.

2. Cableado del interruptor de láminas

Un interruptor de láminas tiene dos terminales y no está polarizado (la orientación no importa).
* Terminal 1: Conéctelo al pin digital 2 en el Arduino.
* Terminal 2: Conéctelo a un pin GND en el Arduino.
* Nota lógica: Configuraremos el pin 2 como INPUT_PULLUP. Cuando el imán está cerca (puerta cerrada), el interruptor se cierra, conectando el pin 2 a GND (leyendo LOW). Cuando la puerta se abre, el interruptor se abre y la resistencia interna eleva el pin 2 a 5V (leyendo HIGH).

3. Cableado del zumbador piezoeléctrico

Si su zumbador piezoeléctrico tiene una pata más larga (positiva) y una pata más corta (negativa), siga esta polarización.
* Terminal positivo (Pata más larga / Cable rojo): Conéctelo al pin digital 8 en el Arduino.
* Terminal negativo (Pata más corta / Cable negro): Conéctelo a un pin GND en el Arduino.

Sketch de Arduino

Cree un nuevo directorio llamado FridgeAlarm y cree el siguiente archivo dentro de él llamado FridgeAlarm.ino.

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

/*
 * Fridge Door and Temperature Alarm
 * Architecture: Arduino UNO R3 (ATmega328P)
 * Sensors: Reed Switch (Pin 2), LM35 (Pin A0)
 * Actuator: Piezo Buzzer (Pin 8)
 */

// Hardware Pin Definitions
const int REED_PIN = 2;
const int BUZZER_PIN = 8;
const int LM35_PIN = A0;

// Configuration Thresholds
const float TEMP_THRESHOLD_C = 8.0;          // Alarm triggers if temperature exceeds 8.0 Celsius
const unsigned long DOOR_TIMEOUT_MS = 20000; // Alarm triggers if door is open for 20,000 ms (20 seconds)
const unsigned long TEMP_READ_INTERVAL = 2000; // Read temperature every 2,000 ms (2 seconds)

// State Tracking Variables
unsigned long doorOpenedAt = 0;
unsigned long lastTempRead = 0;

bool isDoorOpen = false;
bool tempAlarmActive = false;
bool doorAlarmActive = false;
float currentTemp = 0.0;

void setup() {
    // Initialize serial communication for data logging
    Serial.begin(115200);

    // Configure pins
    // INPUT_PULLUP applies an internal 20k-50k ohm resistor to 5V.
    pinMode(REED_PIN, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT);

    Serial.println("--- Fridge Monitor Initialized ---");
    Serial.print("Temp Threshold: ");
    Serial.print(TEMP_THRESHOLD_C);
    Serial.println(" C");
    Serial.print("Door Timeout: ");
    Serial.print(DOOR_TIMEOUT_MS / 1000);
    Serial.println(" seconds");
}

void loop() {
    // Capture current time once per loop iteration
    unsigned long currentMillis = millis();

    // ---------------------------------------------------------
    // 1. Evaluate Door State
    // ---------------------------------------------------------
    // Switch is closed (LOW) when magnet is present (door closed).
    // Switch is open (HIGH) when magnet is removed (door open).
    bool currentDoorState = (digitalRead(REED_PIN) == HIGH);

    // Detect state change: Door just opened
    if (currentDoorState && !isDoorOpen) {
        isDoorOpen = true;
        doorOpenedAt = currentMillis;
        Serial.println("[EVENT] Door Opened.");
    } 
    // Detect state change: Door just closed
    else if (!currentDoorState && isDoorOpen) {
        isDoorOpen = false;
        doorAlarmActive = false;
        Serial.println("[EVENT] Door Closed.");
        noTone(BUZZER_PIN); // Silence any active door alarms
    }
// ...

/*
 * Fridge Door and Temperature Alarm
 * Architecture: Arduino UNO R3 (ATmega328P)
 * Sensors: Reed Switch (Pin 2), LM35 (Pin A0)
 * Actuator: Piezo Buzzer (Pin 8)
 */

// Hardware Pin Definitions
const int REED_PIN = 2;
const int BUZZER_PIN = 8;
const int LM35_PIN = A0;

// Configuration Thresholds
const float TEMP_THRESHOLD_C = 8.0;          // Alarm triggers if temperature exceeds 8.0 Celsius
const unsigned long DOOR_TIMEOUT_MS = 20000; // Alarm triggers if door is open for 20,000 ms (20 seconds)
const unsigned long TEMP_READ_INTERVAL = 2000; // Read temperature every 2,000 ms (2 seconds)

// State Tracking Variables
unsigned long doorOpenedAt = 0;
unsigned long lastTempRead = 0;

bool isDoorOpen = false;
bool tempAlarmActive = false;
bool doorAlarmActive = false;
float currentTemp = 0.0;

void setup() {
    // Initialize serial communication for data logging
    Serial.begin(115200);

    // Configure pins
    // INPUT_PULLUP applies an internal 20k-50k ohm resistor to 5V.
    pinMode(REED_PIN, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT);

    Serial.println("--- Fridge Monitor Initialized ---");
    Serial.print("Temp Threshold: ");
    Serial.print(TEMP_THRESHOLD_C);
    Serial.println(" C");
    Serial.print("Door Timeout: ");
    Serial.print(DOOR_TIMEOUT_MS / 1000);
    Serial.println(" seconds");
}

void loop() {
    // Capture current time once per loop iteration
    unsigned long currentMillis = millis();

    // ---------------------------------------------------------
    // 1. Evaluate Door State
    // ---------------------------------------------------------
    // Switch is closed (LOW) when magnet is present (door closed).
    // Switch is open (HIGH) when magnet is removed (door open).
    bool currentDoorState = (digitalRead(REED_PIN) == HIGH);

    // Detect state change: Door just opened
    if (currentDoorState && !isDoorOpen) {
        isDoorOpen = true;
        doorOpenedAt = currentMillis;
        Serial.println("[EVENT] Door Opened.");
    } 
    // Detect state change: Door just closed
    else if (!currentDoorState && isDoorOpen) {
        isDoorOpen = false;
        doorAlarmActive = false;
        Serial.println("[EVENT] Door Closed.");
        noTone(BUZZER_PIN); // Silence any active door alarms
    }

    // Check if door has been open longer than the timeout
    if (isDoorOpen && (currentMillis - doorOpenedAt >= DOOR_TIMEOUT_MS)) {
        if (!doorAlarmActive) {
            Serial.println("[ALARM] Door Open Timeout Exceeded!");
            doorAlarmActive = true;
        }
    }

    // ---------------------------------------------------------
    // 2. Evaluate Temperature
    // ---------------------------------------------------------
    if (currentMillis - lastTempRead >= TEMP_READ_INTERVAL) {
        lastTempRead = currentMillis;

        // Read 10-bit ADC value (0-1023)
        int adcValue = analogRead(LM35_PIN);

        // Convert ADC value to voltage (5V reference)
        // Note: 1024.0 is the correct divisor for a 10-bit AVR ADC
        float voltage = (adcValue / 1024.0) * 5.0;

        // LM35 outputs 10mV (0.01V) per degree Celsius
        currentTemp = voltage / 0.01;

        Serial.print("[DATA] Current Temp: ");
        Serial.print(currentTemp);
        Serial.println(" C");

        // Trigger temperature alarm if threshold is exceeded
        if (currentTemp > TEMP_THRESHOLD_C) {
            if (!tempAlarmActive) {
                Serial.println("[ALARM] Temperature Critical!");
                tempAlarmActive = true;
            }
        } else {
            if (tempAlarmActive) {
                Serial.println("[RESOLVED] Temperature returned to normal.");
                tempAlarmActive = false;
                noTone(BUZZER_PIN); // Silence temp alarm
            }
        }
    }

    // ---------------------------------------------------------
    // 3. Actuate Alarms (Non-blocking Buzzer Logic)
    // ---------------------------------------------------------
    // Priority 1: Door Alarm (Fast high-pitched beep)
    if (doorAlarmActive) {
        // Toggle tone every 250ms
        if ((currentMillis / 250) % 2 == 0) {
            tone(BUZZER_PIN, 1200); // 1200 Hz
        } else {
            noTone(BUZZER_PIN);
        }
    } 
    // Priority 2: Temperature Alarm (Slow lower-pitched beep)
    // Only sound if the door alarm isn't already sounding
    else if (tempAlarmActive && !doorAlarmActive) {
        // Toggle tone every 1000ms
        if ((currentMillis / 1000) % 2 == 0) {
            tone(BUZZER_PIN, 600); // 600 Hz
        } else {
            noTone(BUZZER_PIN);
        }
    }
    // No alarms active
    else if (!doorAlarmActive && !tempAlarmActive) {
        noTone(BUZZER_PIN);
    }
}

Comandos de compilación y subida

Navegue hasta el directorio justo por encima de su carpeta FridgeAlarm en su terminal. Asegúrese de que su Arduino UNO R3 esté conectado a su computadora por USB. Identifique su puerto serie (por ejemplo, COM3 en Windows, /dev/ttyACM0 en Linux o /dev/cu.usbmodem14101 en macOS) y ejecute los siguientes comandos exactos para preparar el entorno, compilar y subir el sketch.

# Update the core index
arduino-cli core update-index

# Install the AVR core for Arduino UNO
arduino-cli core install arduino:avr

# Compile the sketch for the Arduino UNO R3
arduino-cli compile --fqbn arduino:avr:uno FridgeAlarm

# Upload the compiled code to the board (replace <PORT> with your actual port)
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> FridgeAlarm

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 prototipo descrito en el artículo?




Pregunta 2: ¿Qué problema busca evitar este sistema en el almacenamiento comercial y de laboratorio?




Pregunta 3: ¿Qué consecuencia negativa previene el sistema en entornos residenciales?




Pregunta 4: ¿Qué función de programación se utiliza para manejar múltiples sensores sin bloqueo de subprocesos?




Pregunta 5: ¿Qué tipo de sensor de temperatura se menciona en el resultado esperado?




Pregunta 6: ¿Con qué frecuencia el sistema consulta el sensor de temperatura?




Pregunta 7: ¿A dónde se transmiten los datos de temperatura para su registro?




Pregunta 8: ¿Qué componente se utiliza para detectar con precisión los estados de la puerta?




Pregunta 9: ¿Qué concepto educativo de hardware demuestra este proyecto?




Pregunta 10: Además del desperdicio de energía, ¿qué otro problema principal busca evitar el monitoreo de la temperatura interna?




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: