Módulo 7 - Proyecto Final

Sistema Multitarea IoT Completo

Integración de Sensores, WiFi y FreeRTOS en Tiempo Real

ESP32 FreeRTOS WiFi IoT UNAM

Proyecto Final: Sistema IoT Multitarea

Sistema de Monitoreo Industrial en Tiempo Real

Este proyecto integra todos los conceptos del módulo 7 en un sistema industrial completo que utiliza multitasking con FreeRTOS para manejar sensores, comunicación WiFi, y visualización en tiempo real.

El sistema implementa 4 tareas concurrentes con diferentes prioridades, comunicación mediante colas y semáforos, y un servidor web embebido para monitoreo remoto.

🎯 Objetivos de Aprendizaje:
  • ✅ Integrar multitasking avanzado
  • ✅ Implementar comunicación IoT
  • ✅ Gestionar prioridades en tiempo real
  • ✅ Desarrollar interfaz web embebida
🏭 Aplicaciones Industriales:
  • 🏭 Automatización de procesos
  • 📊 Monitoreo de calidad
  • 🔧 Mantenimiento predictivo
  • 📡 Telemetría en tiempo real

Arquitectura del Sistema Multitarea

Flujo de Datos en Tiempo Real

Sensores
Temp, Humedad, Presión
ESP32
Procesamiento Dual-Core
WiFi
Comunicación IoT
Dashboard
Visualización Web

Tarea Sensores

Prioridad: 4 (Alta)

Función: Lectura continua de sensores DHT22 y BMP280

Frecuencia: 1Hz con filtrado digital

Núcleo: Core 0

Tarea WiFi

Prioridad: 3 (Media-Alta)

Función: Servidor web y cliente HTTP

Protocolo: REST API + WebSocket

Núcleo: Core 1

Tarea Display

Prioridad: 2 (Normal)

Función: Actualización OLED local

Interfaz: I2C con mutex protection

Núcleo: Core 0

Implementación Completa del Sistema

El siguiente código implementa un sistema completo que integra todos los conceptos del módulo:

C++ - Sistema IoT Multitarea Completo
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// Configuración de red WiFi
const char* ssid = "TU_RED_WIFI";
const char* password = "TU_PASSWORD";

// Configuración de sensores
#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

// Display OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Sensores I2C
Adafruit_BMP280 bmp;

// Servidor web
WebServer server(80);

// Variables globales del sistema
struct SensorData {
  float temperature;
  float humidity;
  float pressure;
  unsigned long timestamp;
  bool valid;
};

// Colas para comunicación entre tareas
QueueHandle_t sensorQueue;
QueueHandle_t displayQueue;
QueueHandle_t wifiQueue;

// Semáforos para sincronización
SemaphoreHandle_t i2cMutex;
SemaphoreHandle_t dataMutex;
SemaphoreHandle_t wifiSemaphore;

// Handles de tareas
TaskHandle_t sensorTaskHandle = NULL;
TaskHandle_t wifiTaskHandle = NULL;
TaskHandle_t displayTaskHandle = NULL;
TaskHandle_t monitorTaskHandle = NULL;

// Variables de estado del sistema
volatile bool systemRunning = true;
volatile bool wifiConnected = false;
SensorData latestData = {0, 0, 0, 0, false};

// TAREA SENSORES (Prioridad 4) - Core 0
void sensorTask(void *pvParameters) {
  Serial.println("[SENSOR] Iniciando tarea de sensores...");
  
  // Inicializar sensores
  dht.begin();
  
  if (!bmp.begin(0x76)) {
    Serial.println("[SENSOR] Error: No se pudo inicializar BMP280");
    vTaskDelete(NULL);
  }
  
  // Configurar BMP280
  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
                  Adafruit_BMP280::SAMPLING_X2,
                  Adafruit_BMP280::SAMPLING_X16,
                  Adafruit_BMP280::FILTER_X16,
                  Adafruit_BMP280::STANDBY_MS_500);
  
  SensorData data;
  TickType_t lastWakeTime = xTaskGetTickCount();
  
  for (;;) {
    // Leer sensores con timeout
    data.timestamp = millis();
    data.valid = true;
    
    // Leer DHT22 (temperatura y humedad)
    data.temperature = dht.readTemperature();
    data.humidity = dht.readHumidity();
    
    if (isnan(data.temperature) || isnan(data.humidity)) {
      Serial.println("[SENSOR] Warning: Error leyendo DHT22");
      data.valid = false;
    }
    
    // Leer BMP280 (presión) con mutex I2C
    if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100)) == pdPASS) {
      data.pressure = bmp.readPressure() / 100.0F; // hPa
      xSemaphoreGive(i2cMutex);
    } else {
      Serial.println("[SENSOR] Warning: Timeout I2C mutex");
      data.valid = false;
    }
    
    if (data.valid) {
      Serial.printf("[SENSOR] T:%.1f°C H:%.1f%% P:%.1fhPa\n", 
                    data.temperature, data.humidity, data.pressure);
      
      // Actualizar datos globales con mutex
      if (xSemaphoreTake(dataMutex, pdMS_TO_TICKS(50)) == pdPASS) {
        latestData = data;
        xSemaphoreGive(dataMutex);
      }
      
      // Enviar a colas (no bloqueante)
      xQueueSend(displayQueue, &data, 0);
      xQueueSend(wifiQueue, &data, 0);
    }
    
    // Dormir hasta próxima lectura (1Hz)
    vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(1000));
  }
}

// TAREA WiFi (Prioridad 3) - Core 1  
void wifiTask(void *pvParameters) {
  Serial.println("[WIFI] Iniciando tarea WiFi...");
  
  // Conectar a WiFi
  WiFi.begin(ssid, password);
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    vTaskDelay(pdMS_TO_TICKS(500));
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    wifiConnected = true;
    Serial.printf("\n[WIFI] Conectado a %s\n", ssid);
    Serial.printf("[WIFI] IP: %s\n", WiFi.localIP().toString().c_str());
    
    // Configurar rutas del servidor web
    setupWebServer();
    server.begin();
    
    // Señalizar que WiFi está listo
    xSemaphoreGive(wifiSemaphore);
  } else {
    Serial.println("\n[WIFI] Error: No se pudo conectar");
    wifiConnected = false;
  }
  
  SensorData data;
  
  for (;;) {
    if (wifiConnected) {
      // Manejar clientes web
      server.handleClient();
      
      // Procesar datos de sensores
      if (xQueueReceive(wifiQueue, &data, pdMS_TO_TICKS(100)) == pdPASS) {
        // Aquí se podrían enviar datos a servicios cloud
        // Por ejemplo: ThingSpeak, AWS IoT, etc.
        Serial.printf("[WIFI] Datos listos para transmisión web\n");
      }
      
      // Verificar conexión WiFi
      if (WiFi.status() != WL_CONNECTED) {
        Serial.println("[WIFI] Conexión perdida, reintentando...");
        wifiConnected = false;
        WiFi.reconnect();
      }
    } else {
      // Reintentar conexión
      vTaskDelay(pdMS_TO_TICKS(5000));
      WiFi.begin(ssid, password);
    }
    
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}

// TAREA DISPLAY (Prioridad 2) - Core 0
void displayTask(void *pvParameters) {
  Serial.println("[DISPLAY] Iniciando tarea de display...");
  
  // Inicializar display con mutex I2C
  if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
      Serial.println("[DISPLAY] Error: No se pudo inicializar OLED");
      xSemaphoreGive(i2cMutex);
      vTaskDelete(NULL);
    }
    
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(0, 0);
    display.println("Sistema IoT");
    display.println("Iniciando...");
    display.display();
    
    xSemaphoreGive(i2cMutex);
  }
  
  SensorData data;
  unsigned long lastUpdate = 0;
  
  for (;;) {
    if (xQueueReceive(displayQueue, &data, pdMS_TO_TICKS(500)) == pdPASS) {
      
      // Actualizar display con mutex I2C
      if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100)) == pdPASS) {
        display.clearDisplay();
        display.setCursor(0, 0);
        
        // Título
        display.setTextSize(1);
        display.println("=== IoT Monitor ===");
        
        // Datos de sensores
        display.printf("Temp: %.1f C\n", data.temperature);
        display.printf("Hum:  %.1f %%\n", data.humidity);
        display.printf("Pres: %.0f hPa\n", data.pressure);
        
        // Estado del sistema
        display.println("---Status---");
        display.printf("WiFi: %s\n", wifiConnected ? "OK" : "X");
        display.printf("Up: %lu s\n", millis() / 1000);
        
        display.display();
        xSemaphoreGive(i2cMutex);
        
        lastUpdate = millis();
      }
    } else {
      // Timeout - mostrar mensaje de estado
      if (millis() - lastUpdate > 10000) {
        if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100)) == pdPASS) {
          display.clearDisplay();
          display.setCursor(0, 0);
          display.println("Sistema activo");
          display.printf("Sin datos: %lu s\n", (millis() - lastUpdate) / 1000);
          display.display();
          xSemaphoreGive(i2cMutex);
        }
      }
    }
    
    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

// TAREA MONITOR (Prioridad 1) - Core 0
void monitorTask(void *pvParameters) {
  Serial.println("[MONITOR] Iniciando tarea de monitoreo...");
  
  for (;;) {
    Serial.println("\n=== ESTADO DEL SISTEMA ===");
    Serial.printf("Heap libre: %d bytes\n", ESP.getFreeHeap());
    Serial.printf("Tiempo activo: %lu segundos\n", millis() / 1000);
    
    // Estado de las colas
    Serial.printf("Cola sensores: %d/%d\n", 
                  uxQueueMessagesWaiting(sensorQueue), 
                  uxQueueSpacesAvailable(sensorQueue) + uxQueueMessagesWaiting(sensorQueue));
    Serial.printf("Cola display: %d/%d\n", 
                  uxQueueMessagesWaiting(displayQueue), 
                  uxQueueSpacesAvailable(displayQueue) + uxQueueMessagesWaiting(displayQueue));
    Serial.printf("Cola WiFi: %d/%d\n", 
                  uxQueueMessagesWaiting(wifiQueue), 
                  uxQueueSpacesAvailable(wifiQueue) + uxQueueMessagesWaiting(wifiQueue));
    
    // Stack de tareas
    if (sensorTaskHandle) {
      Serial.printf("Sensor stack: %d bytes libres\n", 
                    uxTaskGetStackHighWaterMark(sensorTaskHandle) * sizeof(StackType_t));
    }
    if (wifiTaskHandle) {
      Serial.printf("WiFi stack: %d bytes libres\n", 
                    uxTaskGetStackHighWaterMark(wifiTaskHandle) * sizeof(StackType_t));
    }
    
    // Datos actuales
    if (xSemaphoreTake(dataMutex, pdMS_TO_TICKS(100)) == pdPASS) {
      if (latestData.valid) {
        Serial.printf("Última lectura: %.1f°C, %.1f%%, %.1fhPa\n", 
                      latestData.temperature, latestData.humidity, latestData.pressure);
      }
      xSemaphoreGive(dataMutex);
    }
    
    Serial.println("==========================\n");
    
    vTaskDelay(pdMS_TO_TICKS(15000)); // Reporte cada 15 segundos
  }
}

// Configuración del servidor web
void setupWebServer() {
  // Página principal
  server.on("/", HTTP_GET, []() {
    String html = R"(



    Sistema IoT ESP32
    
    
    
    


    

🏭 Sistema IoT ESP32

Sistema en línea

🌡️ Temperatura

-- °C

💧 Humedad

-- %

🌬️ Presión

-- hPa

Última actualización: --

)"; server.send(200, "text/html", html); }); // API para datos JSON server.on("/api/data", HTTP_GET, []() { DynamicJsonDocument doc(1024); if (xSemaphoreTake(dataMutex, pdMS_TO_TICKS(100)) == pdPASS) { doc["temperature"] = latestData.temperature; doc["humidity"] = latestData.humidity; doc["pressure"] = latestData.pressure; doc["timestamp"] = latestData.timestamp; doc["valid"] = latestData.valid; xSemaphoreGive(dataMutex); } else { doc["error"] = "Timeout accediendo a datos"; } doc["uptime"] = millis(); doc["heap"] = ESP.getFreeHeap(); String response; serializeJson(doc, response); server.send(200, "application/json", response); }); server.on("/api/status", HTTP_GET, []() { DynamicJsonDocument doc(512); doc["wifi"] = wifiConnected; doc["ip"] = WiFi.localIP().toString(); doc["ssid"] = WiFi.SSID(); doc["rssi"] = WiFi.RSSI(); doc["uptime"] = millis(); String response; serializeJson(doc, response); server.send(200, "application/json", response); }); } void setup() { Serial.begin(115200); delay(2000); Serial.println("\n=== SISTEMA IoT MULTITAREA ESP32 ==="); Serial.println("Módulo 7 - Proyecto Final"); Serial.println("======================================\n"); // Inicializar I2C Wire.begin(); // Crear semáforos i2cMutex = xSemaphoreCreateMutex(); dataMutex = xSemaphoreCreateMutex(); wifiSemaphore = xSemaphoreCreateBinary(); if (i2cMutex == NULL || dataMutex == NULL || wifiSemaphore == NULL) { Serial.println("ERROR: No se pudieron crear los semáforos"); return; } // Crear colas sensorQueue = xQueueCreate(5, sizeof(SensorData)); displayQueue = xQueueCreate(3, sizeof(SensorData)); wifiQueue = xQueueCreate(10, sizeof(SensorData)); if (sensorQueue == NULL || displayQueue == NULL || wifiQueue == NULL) { Serial.println("ERROR: No se pudieron crear las colas"); return; } Serial.println("✅ Semáforos y colas creados exitosamente"); // Crear tareas con prioridades específicas xTaskCreatePinnedToCore(sensorTask, "SensorTask", 4096, NULL, 4, &sensorTaskHandle, 0); xTaskCreatePinnedToCore(wifiTask, "WiFiTask", 8192, NULL, 3, &wifiTaskHandle, 1); xTaskCreatePinnedToCore(displayTask, "DisplayTask", 3072, NULL, 2, &displayTaskHandle, 0); xTaskCreatePinnedToCore(monitorTask, "MonitorTask", 3072, NULL, 1, &monitorTaskHandle, 0); Serial.println("🚀 Todas las tareas creadas exitosamente"); Serial.println("📊 Monitoreo disponible en: http://" + WiFi.localIP().toString()); Serial.println("🔧 Sistema iniciado - verificar monitor serial para detalles\n"); } void loop() { // Loop principal vacío - todo controlado por tareas FreeRTOS vTaskDelete(NULL); }

Guía de Implementación Paso a Paso

Proceso de Desarrollo

1
Preparación del Hardware

Conexiones:

  • DHT22: Pin 4 (con resistencia pull-up 4.7kΩ)
  • BMP280: I2C (SDA=21, SCL=22)
  • OLED: I2C (SDA=21, SCL=22, dirección 0x3C)
  • LED indicador: Pin 2

Verificar: Continuidad de conexiones y alimentación 3.3V

2
Instalación de Librerías

Librerías requeridas (Arduino IDE):

  • DHT sensor library by Adafruit
  • Adafruit BMP280 Library
  • Adafruit SSD1306
  • ArduinoJson (v6.x)
  • ESP32 WebServer
3
Configuración WiFi

Editar credenciales:

  • Cambiar ssid por el nombre de tu red WiFi
  • Cambiar password por la contraseña de tu red
  • Verificar que tu router permite conexiones ESP32
4
Compilación y Upload

Configuración de compilación:

  • Board: ESP32 Dev Module
  • CPU Frequency: 240MHz
  • Flash Size: 4MB
  • Partition Scheme: Default 4MB
5
Verificación y Testing

Comprobar funcionamiento:

  • Monitor Serial: Datos de sensores cada segundo
  • Display OLED: Información local actualizada
  • Página Web: Acceder vía IP mostrada en Serial
  • API REST: Probar endpoint /api/data

Testing y Métricas de Rendimiento

Parámetros de Rendimiento Esperados

1 Hz
Frecuencia de sensores
< 50ms
Respuesta web
> 200KB
Heap libre
4
Tareas activas
99.9%
Uptime objetivo
< 5ms
Context switch
🧪 Pruebas de Validación:
  • Stress Test: Funcionamiento continuo 24h
  • Network Test: Pérdida/reconexión WiFi
  • Sensor Test: Desconexión/reconexión sensores
  • Memory Test: Monitoring de memory leaks
  • Priority Test: Comportamiento bajo carga
  • Recovery Test: Reinicio automático en errores

Extensiones y Mejoras Propuestas

🎯 Mejoras de Funcionalidad

  • Almacenamiento SD: Logging histórico de datos
  • OTA Updates: Actualización remota del firmware
  • MQTT Client: Integración con brokers IoT
  • Alertas SMS/Email: Notificaciones críticas
  • Dashboard avanzado: Gráficos en tiempo real
  • Control remoto: Actuadores via web

⚡ Optimizaciones Avanzadas

  • Deep Sleep: Ahorro energético inteligente
  • Watchdog: Reset automático en cuelgues
  • Calibración: Auto-calibración de sensores
  • Compresión: Reducir tráfico de red
  • Caching: Optimización de respuesta web
  • Security: HTTPS y autenticación
Desafío para Estudiantes

Implementa 2 mejoras adicionales de tu elección y documenta el impacto en el rendimiento del sistema. Considera aspectos como consumo de memoria, latencia, y estabilidad.

Referencias y Documentación Técnica