Medición de CO₂ con Sensor SCD41 y ESP32
📋 Descripción del Proyecto
Sistema de monitoreo de calidad del aire diseñado para aulas escolares que mide la concentración de CO₂ en tiempo real y proporciona alertas visuales y recomendaciones para mantener un ambiente de aprendizaje saludable.
🎯 Aplicación
La calidad del aire interior está directamente relacionada con el rendimiento cognitivo de los estudiantes. Niveles elevados de CO₂ pueden causar:
- Disminución de la concentración
- Somnolencia
- Reducción del rendimiento académico
- Problemas de salud a largo plazo
Este monitor ayuda a tomar decisiones para mantener los niveles óptimos de CO₂ para un ambiente de aprendizaje saludable.
📈 Beneficios Educativos
Para Estudiantes
- Concienciación sobre calidad del aire
- Comprensión de conceptos de química y física
- Aprendizaje sobre sistemas embebidos y sensores
Para Docentes
- Herramienta objetiva para gestión del aula
- Datos para proyectos STEAM
- Mejora del ambiente de aprendizaje
Para el Centro Educativo
- Cumplimiento de normativas de calidad del aire
- Reducción de enfermedades respiratorias
- Optimización del rendimiento académico
🔧 Hardware Utilizado
En la imagen de la izquierda, el sensor SCD41 y el led neopixel sobre la controladora Lolin D32 (ESP32)
A la derecha, pantalla OLED
A la derecha, pantalla OLED
Componentes Principales
Componente | Modelo/Especificaciones | Función |
---|---|---|
Microcontrolador | Lolin D32 (ESP32) | Control principal del sistema |
Sensor de CO2 | SCD41 (Sensirion) | Medición de CO2, temperatura y humedad |
Pantalla | OLED SH1106 (128x64 px) | Visualización de datos |
Indicador Visual | LED NeoPixel WS2812B | Alertas de color por estado |
Especificaciones del Sensor SCD41
- Rango de medición CO2: 400 - 40,000 ppm
- Precisión: ±50 ppm + 5% de la lectura
- Interfaz: I2C
- Consumo: <0.5 mA (modo bajo consumo)
- Tiempo de respuesta: <60 segundos
- Calibración: Automática con referencia de 400 ppm
🔌 Esquema de Conexionado
Bus I2C Principal (Bus #2)
ESP32 → Dispositivos I2C
Pin 19 (SDA) → SDA Sensor SCD41 + OLED
Pin 22 (SCL) → SCL Sensor SCD41 + OLED
3.3V → VCC Sensor + OLED
GND → GND Sensor + OLED
LED de Estado
ESP32 → LED NeoPixel
Pin 4 → DIN (Entrada de datos)
3.3V → VCC
GND → GND
Direcciones I2C
- Sensor SCD41: 0x62
- Pantalla OLED: 0x3C
- Velocidad del bus: 50 kHz (optimizada para estabilidad)
⚙️ Funciones del Sistema
Estados de Monitoreo
Estado | Rango CO2 | LED | Pantalla | Acción Recomendada |
---|---|---|---|---|
NORMAL | ≤ 1000 ppm | 🟢 Verde (brillo bajo) | Apagada | Aire óptimo |
ADVERTENCIA | 1001-1600 ppm | 🟠 Naranja (brillo medio) | Encendida | Abrir puerta |
CRÍTICO | > 1600 ppm | 🔴 Rojo fijo (brillo alto) | Alerta visible | Ventilar urgente |
Gestión Inteligente de Energía
- Intervalos de medición adaptativos:
- Normal: 180 segundos
- Advertencia: 60 segundos
- Crítico: 20 segundos
📊 Interpretación de Mediciones
Niveles de CO2 en Ambientes Escolares
Concentración | Calidad del Aire | Efectos en el Aprendizaje |
---|---|---|
350-400 ppm | Aire exterior | Referencia natural |
400-1000 ppm | Excelente | Condiciones óptimas |
1000-1600 ppm | Aceptable | Ligera disminución concentración |
1600-5000 ppm | Pobre | Somnolencia, fatiga |
>5000 ppm | Muy pobre | Problemas de salud |
Recomendaciones de Ventilación
- < 800 ppm: Mantener ventilación normal
- 800-1200 ppm: Aumentar ventilación natural (ventanas semiabiertas)
- 1200-1600 ppm: Abrir puerta prioritariamente, alternativamente ventanas
- > 1600 ppm: Ventilación cruzada urgente (puerta y ventanas) o evacuación temporal
🔋 Características de Eficiencia Energética
Optimizaciones Implementadas
- Reducción de frecuencia CPU (240MHz → 3MHz)
- Comunicaciones inalámbricas desactivadas
- Modo de suspensión inteligente (light sleep)
- Pantalla apagada en estado normal
- LEDs con brillo optimizado
Consumo Estimado
- Modo activo: ~15 mA
- Modo suspensión: ~2 mA
- Autonomía con batería 2000mAh: ~120 horas (5 días)
🛠️ Instalación y Configuración
Requisitos de Software
- Arduino IDE 1.8.19 o superior
- Bibliotecas requeridas:
SparkFun_SCD4x_Arduino_Library
Adafruit_NeoPixel
Adafruit_SH110X
Adafruit_GFX
/* * Monitor de CO2 con sensor SCD41 y ESP32 * wwww.acusticaescolar.com * Creative commons * *Este código implementa un sistema de monitoreo de CO2 que: * - Lee datos del sensor SCD41 a través de I2C * - Muestra información en pantalla OLED * - Indica niveles de CO2 con LED NeoPixel * - Optimizado para bajo consumo energético * * Estados del sistema basados en niveles de CO2: * - NORMAL: ≤1000 ppm (LED verde) * - WARNING: 1000-1600 ppm (LED naranja fijo) * - CRITICAL: ≥1600 ppm (LED rojo fijo) */ #include <Wire.h> #include <Adafruit_NeoPixel.h> #include "SparkFun_SCD4x_Arduino_Library.h" #include <Adafruit_GFX.h> #include <Adafruit_SH110X.h> #include "esp_wifi.h" #include "esp_bt.h" // ================================ // CONFIGURACIÓN DEL SISTEMA // ================================ // Configuración de debug - cambiar a 1 para activar mensajes de depuración #define DEBUG_MODE 0 // 0 = producción, 1 = debug // Definición de pines para los buses I2C duales // Bus I2C #1 (actualmente no utilizado en este código) #define I2C_SDA_1 33 #define I2C_SCL_1 32 // Bus I2C #2 - usado para sensor SCD41 y pantalla OLED #define I2C_SDA_2 19 #define I2C_SCL_2 22 // Direcciones I2C de los dispositivos #define OLED_ADDR 0x3C // Dirección de la pantalla OLED #define SCD41_ADDR 0x62 // Dirección del sensor SCD41 // Configuración de la pantalla OLED #define SCREEN_WIDTH 128 // Ancho en píxeles #define SCREEN_HEIGHT 64 // Alto en píxeles #define OLED_RESET -1 // Pin de reset (-1 si no se usa) // ================================ // DEFINICIÓN DE ESTADOS DEL SISTEMA // ================================ // Enumeración de estados basados en niveles de CO2 typedef enum { STATE_NORMAL, // CO2 <= 1000 ppm - Aire bueno STATE_WARNING, // 1000 < CO2 < 1600 ppm - Ventilación recomendada STATE_CRITICAL // CO2 >= 1600 ppm - Ventilación urgente } co2_state_t; // ================================ // CREACIÓN DE INSTANCIAS DE OBJETOS // ================================ // Crear los buses I2C separados TwoWire I2C_MPU = TwoWire(0); // Bus I2C #1 (no usado actualmente) TwoWire I2C_SCD41_OLED = TwoWire(1); // Bus I2C #2 para sensor y OLED // Crear instancia de la pantalla OLED SH1106 Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &I2C_SCD41_OLED, OLED_RESET); // Crear instancia del sensor SCD41 SCD4x mySensor; // ================================ // VARIABLES GLOBALES // ================================ // Variables para almacenar las lecturas del sensor uint16_t co2 = 0; // Concentración de CO2 en ppm float temperature = 0.0; // Temperatura en °C (no utilizada actualmente) float humidity = 0.0; // Humedad relativa en % (no utilizada actualmente) // Variables de temporización unsigned long lastUpdate = 0; // Última actualización const int updateInterval = 5000; // Intervalo de actualización en ms // Configuración del LED NeoPixel #define LED_PIN 4 // Pin donde está conectado el LED #define LED_COUNT 1 // Número de LEDs (solo uno) Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); // Variables de estado del sistema co2_state_t current_state = STATE_NORMAL; // Estado actual del sistema co2_state_t previous_state = STATE_NORMAL; // Estado anterior (para detectar cambios) // ================================ // FUNCIÓN DE CONFIGURACIÓN INICIAL // ================================ void setup() { // Reducir frecuencia del CPU para ahorrar energía (de 240MHz a 3MHz) setCpuFrequencyMhz(3); // Inicializar comunicación serie solo en modo debug #if DEBUG_MODE Serial.begin(115200); Serial.println("ESP32 Dual I2C - OLED y Sensor CO2 SCD41 [DEBUG MODE]"); #endif // Desactivar WiFi y Bluetooth para reducir consumo energético esp_wifi_deinit(); // Desactivar WiFi completamente esp_bt_controller_disable(); // Desactivar Bluetooth // Inicializar el bus I2C #2 con velocidad reducida para estabilidad // Velocidad reducida de 100kHz a 50kHz para mejor estabilidad I2C_SCD41_OLED.begin(I2C_SDA_2, I2C_SCL_2, 50000); // Inicializar el LED NeoPixel strip.begin(); // Configurar el LED strip.show(); // Asegurar que inicie apagado // ================================ // INICIALIZACIÓN DE LA PANTALLA OLED // ================================ display.begin(OLED_ADDR, true); // Inicializar con dirección I2C display.clearDisplay(); // Limpiar pantalla display.setTextSize(1); // Tamaño de texto pequeño display.setTextColor(SH110X_WHITE); // Color blanco display.setCursor(0, 0); // Posición inicial display.println("Iniciant..."); // Mensaje de inicio display.display(); // Mostrar en pantalla // ================================ // INICIALIZACIÓN DEL SENSOR SCD41 // ================================ #if DEBUG_MODE Serial.println("Inicializando SCD41..."); #endif // Intentar conectar con el sensor SCD41 if (mySensor.begin(I2C_SCD41_OLED, SCD41_ADDR) == false) { // Si no se puede conectar al sensor, mostrar error #if DEBUG_MODE Serial.println("Error: Sensor SCD41 no encontrado. Verifique conexiones."); #endif // Mostrar mensaje de error en pantalla display.clearDisplay(); display.setCursor(0, 0); display.println("Error: SCD41"); display.println("no encontrado"); display.display(); // Bucle infinito con modo de bajo consumo en caso de error while (1) { esp_sleep_enable_timer_wakeup(5 * 1000000); // Despertar cada 5 segundos esp_light_sleep_start(); // Entrar en modo de bajo consumo } } // ================================ // CONFIGURACIÓN DEL SENSOR SCD41 // ================================ // Detener mediciones periódicas para configurar el sensor if (mySensor.stopPeriodicMeasurement() == true) { #if DEBUG_MODE Serial.println(F("Medidas períódicas desactivadas")); #endif } // Configurar offset de temperatura (compensación por calentamiento del sensor) #if DEBUG_MODE Serial.print(F("Offset de Temperatura actual: ")); Serial.println(mySensor.getTemperatureOffset(), 2); #endif mySensor.setTemperatureOffset(0); // Sin offset de temperatura #if DEBUG_MODE Serial.print(F("Ahora el offset de Temperature es: ")); Serial.println(mySensor.getTemperatureOffset(), 2); Serial.print(F("Altitud actual del sensor: ")); Serial.println(mySensor.getSensorAltitude()); #endif // Configurar altitud del sensor (Els Pallaresos = 121m sobre nivel del mar) // La altitud afecta la calibración de CO2 mySensor.setSensorAltitude(121); #if DEBUG_MODE Serial.print(F("Sensor altitude ahora es: ")); Serial.println(mySensor.getSensorAltitude()); #endif // Configurar presión atmosférica ambiente (en Pascales) // 99787 Pa aproximadamente corresponde a la presión a 121m de altitud bool success = mySensor.setAmbientPressure(99787); #if DEBUG_MODE if (success) { Serial.println(F("setAmbientPressure configurada")); } #endif // Guardar configuración en la memoria no volátil del sensor mySensor.persistSettings(); // Mostrar número de serie del sensor (solo en modo debug) #if DEBUG_MODE char serialNumber[13]; if (mySensor.getSerialNumber(serialNumber) == true) { Serial.print(F("El número de serie del sensor es: 0x")); Serial.println(serialNumber); } #endif // ================================ // ACTIVAR MODO DE BAJO CONSUMO // ================================ // Asegurar que las mediciones periódicas estén detenidas if (mySensor.stopPeriodicMeasurement() == true) { #if DEBUG_MODE Serial.println(F("Medición periódica desactivada")); #endif } // Activar modo de medición periódica de bajo consumo // Este modo toma mediciones cada 30 segundos automáticamente if (mySensor.startLowPowerPeriodicMeasurement() == true) { #if DEBUG_MODE Serial.println(F("Bajo consumo activado")); #endif } // ================================ // MENSAJE FINAL DE INICIALIZACIÓN // ================================ // Mostrar mensaje de inicialización completa en OLED display.clearDisplay(); display.setCursor(0, 0); display.println("SCD41 CO2 Monitor"); display.println("----------------"); display.println("Calentando sensor..."); display.display(); // Esperar 3 segundos usando modo de bajo consumo en lugar de delay() // Esto permite que el sensor se estabilice esp_sleep_enable_timer_wakeup(3 * 1000000); // 3 segundos en microsegundos esp_light_sleep_start(); } // ================================ // BUCLE PRINCIPAL DEL PROGRAMA // ================================ void loop() { // Verificar si el sensor tiene nuevos datos disponibles if (mySensor.readMeasurement()) { // Leer valor de CO2 del sensor co2 = mySensor.getCO2(); #if DEBUG_MODE Serial.print("CO2(ppm): "); Serial.print(co2); Serial.println(); #endif // Actualizar el estado del sistema basado en el nivel de CO2 updateCO2State(); // Manejar LED y pantalla según el estado actual handleDeviceStates(); } #if DEBUG_MODE else { // Mostrar punto si no hay datos disponibles (solo en debug) Serial.print(F(".")); } #endif // ================================ // GESTIÓN INTELIGENTE DEL CONSUMO // ================================ // Determinar tiempo de suspensión basado en el estado actual // En estados críticos, despertarse más frecuentemente uint32_t sleep_time; switch (current_state) { case STATE_NORMAL: sleep_time = 180 * 1000000; // 180 segundos - aire bueno, menos urgencia break; case STATE_WARNING: sleep_time = 60 * 1000000; // 60 segundos - monitoreo más frecuente break; case STATE_CRITICAL: sleep_time = 20 * 1000000; // 20 segundos - monitoreo urgente break; default: sleep_time = 180 * 1000000; // Valor por defecto break; } // Configurar y entrar en modo de bajo consumo esp_sleep_enable_timer_wakeup(sleep_time); esp_light_sleep_start(); } // ================================ // FUNCIONES DE GESTIÓN DE ESTADOS // ================================ /** * Actualiza el estado del sistema basado en el nivel de CO2 * * Rangos de CO2: * - 0-1000 ppm: NORMAL (aire exterior, ambientes bien ventilados) * - 1001-1600 ppm: WARNING (ventilación recomendada) * - >1600 ppm: CRITICAL (ventilación urgente requerida) */ void updateCO2State() { // Guardar el estado anterior para detectar cambios previous_state = current_state; // Determinar nuevo estado basado en nivel de CO2 if (co2 <= 1000) { current_state = STATE_NORMAL; } else if (co2 < 1600) { current_state = STATE_WARNING; } else { current_state = STATE_CRITICAL; } } /** * Maneja los dispositivos (LED y pantalla) según el estado actual * Llama a la función específica para cada estado */ void handleDeviceStates() { switch (current_state) { case STATE_NORMAL: handleNormalState(); break; case STATE_WARNING: handleWarningState(); break; case STATE_CRITICAL: handleCriticalState(); break; } } /** * Maneja el estado NORMAL (CO2 ≤ 1000 ppm) * - LED verde con brillo bajo para ahorrar energía * - Pantalla apagada para máximo ahorro energético */ void handleNormalState() { // Configurar LED verde con brillo reducido strip.setPixelColor(0, 0, 254, 0); // RGB: Verde strip.setBrightness(10); // Brillo muy bajo (de 255 máximo) strip.show(); // Aplicar cambios al LED // Apagar pantalla OLED para ahorrar energía display.oled_command(SH110X_DISPLAYOFF); } /** * Maneja el estado WARNING (1000 < CO2 < 1600 ppm) * - LED naranja fijo para indicar precaución * - Pantalla encendida solo cuando cambia de estado */ void handleWarningState() { // LED naranja fijo para indicar precaución strip.setPixelColor(0, 254, 191, 0); // RGB: Naranja strip.setBrightness(25); // Brillo medio para alerta visible pero eficiente strip.show(); // Actualizar pantalla solo si es la primera vez en este estado // (para ahorrar energía) if (previous_state != current_state) { updateDisplay(); } } /** * Maneja el estado CRITICAL (CO2 ≥ 1600 ppm) * - LED rojo fijo con brillo medio-alto * - Pantalla siempre encendida mostrando alerta */ void handleCriticalState() { // LED rojo fijo para indicar urgencia strip.setPixelColor(0, 254, 0, 0); // RGB: Rojo strip.setBrightness(50); // Brillo más alto para urgencia strip.show(); // Mantener pantalla actualizada en estado crítico updateDisplay(); } /** * Actualiza el contenido de la pantalla OLED * Muestra información diferente según el estado actual */ void updateDisplay() { // Encender la pantalla display.oled_command(SH110X_DISPLAYON); display.clearDisplay(); // ================================ // ENCABEZADO DE LA PANTALLA // ================================ display.setTextSize(1); // Texto pequeño para encabezado display.setCursor(0, 0); // Esquina superior izquierda display.println("Midiendo CO2:"); display.println(""); // Línea en blanco // ================================ // VALOR PRINCIPAL DE CO2 // ================================ display.setTextSize(2); // Texto grande para valor principal display.setCursor(10, 20); // Centrado horizontalmente display.print(co2); display.println(" ppm"); // ================================ // MENSAJES ESPECÍFICOS POR ESTADO // ================================ if (current_state == STATE_WARNING) { // Mensaje de advertencia con instrucciones display.setTextSize(1); display.setCursor(0, 45); display.print("ALERTA! Abrir"); display.setCursor(0, 55); display.print("puerta o ventana"); } else if (current_state == STATE_CRITICAL) { // Mensaje crítico urgente display.setTextSize(2); display.setCursor(10, 45); display.print("VENTILAR"); } // Aplicar todos los cambios a la pantalla display.display(); }
🔬 Posibles Extensiones del Proyecto
- Conectividad IoT: WiFi para monitoreo remoto
- Datalogger: Almacenamiento de datos históricos
- Alertas sonoras: Buzzer para avisos audibles
- Múltiples sensores: Red de monitores en diferentes aulas con app Sensirion MyAmbience
- Interfaz web: Dashboard para administración escolar
📋 Lista de Materiales y Costes
Componente | Cantidad | Precio Aprox. |
---|---|---|
ESP32 DevKit | 1 | 8€ |
Sensor SCD41 | 1 | 45€ (15€ unidades remanufacturadas) |
OLED SH1106 | 1 | 6€ |
LED NeoPixel | 1 | 2€ |
Cables y caja | - | 3€ |
TOTAL | ≈ 64€ (35€ según precio del sensor) |
Proyecto desarrollado por www.acusticaescolar.com para mejorar la calidad del aire en ambientes educativos. Junio 2025.
No hay comentarios:
Publicar un comentario