CO₂ SuperVisor

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

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

  1. Reducción de frecuencia CPU (240MHz → 3MHz)
  2. Comunicaciones inalámbricas desactivadas
  3. Modo de suspensión inteligente (light sleep)
  4. Pantalla apagada en estado normal
  5. 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

  1. Conectividad IoT: WiFi para monitoreo remoto
  2. Datalogger: Almacenamiento de datos históricos
  3. Alertas sonoras: Buzzer para avisos audibles
  4. Múltiples sensores: Red de monitores en diferentes aulas con app Sensirion MyAmbience
  5. 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