Módulo 12

Integración: lectura de sensores + control de actuadores

Proyecto Final Integral

ESP32 Mecatrónica IoT UNAM

Fundamentos de Integración Sensor-Actuador

La integración entre lectura de sensores y control de actuadores representa el núcleo de los sistemas mecatrónicos modernos. Esta sinergia permite crear sistemas inteligentes que pueden percibir su entorno físico, procesar información y ejecutar acciones de respuesta automáticas.

ESP32 como Centro de Control

El ESP32 actúa como unidad central de procesamiento, integrando capacidades avanzadas:

  • ADC de 12 bits para sensores analógicos
  • 18 pines GPIO configurables
  • PWM de alta resolución para control preciso
  • Conectividad WiFi/Bluetooth integrada
Aplicaciones Industriales

Implementaciones críticas en la industria moderna:

  • Control de procesos en tiempo real
  • Sistemas HVAC inteligentes
  • Automatización manufacturera
  • Monitoreo ambiental continuo

Arquitectura de Sistema Integrado

Un sistema mecatrónico efectivo implementa una arquitectura de lazo cerrado donde los sensores proporcionan retroalimentación continua para ajustar el comportamiento de los actuadores:

Principio de Control en Lazo Cerrado
Sensores → Procesamiento → Decisión → Actuadores → Retroalimentación → Sensores

Arquitectura Técnica y Periféricos ESP32

Configuración de Pines GPIO

El ESP32 proporciona 18 pines GPIO multifunción que pueden configurarse dinámicamente según las necesidades del sistema:

Entradas Digitales

Sensores digitales, botones, interruptores

digitalRead()
Salidas Digitales

LEDs, relés, actuadores digitales

digitalWrite()
Salidas PWM

Servos, control de velocidad, dimming

analogWrite()

Sistema ADC y Acondicionamiento de Señal

El conversor ADC de 12 bits del ESP32 proporciona resolución de 4096 niveles, ideal para aplicaciones de precisión:

Control Integrado Sensor-Actuador
#include "DHT.h"
#include "ESP32Servo.h"

// Configuración de hardware
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define LED_PIN 2
#define SERVO_PIN 18
#define POTENTIOMETER_PIN 34

// Instancias de objetos
DHT dht(DHT_PIN, DHT_TYPE);
Servo controlServo;

// Variables de control
float temperatura_objetivo = 25.0;
int intensidad_led = 0;
unsigned long ultimo_muestreo = 0;
const unsigned long INTERVALO_MUESTREO = 1000;

// Estructura para almacenar datos de sensores
struct DatosSensores {
    float temperatura;
    float humedad;
    int potenciometro;
    bool sensor_valido;
};

void setup() {
    Serial.begin(115200);
    
    // Inicialización de periféricos
    dht.begin();
    controlServo.attach(SERVO_PIN);
    
    // Configuración de pines
    pinMode(LED_PIN, OUTPUT);
    pinMode(POTENTIOMETER_PIN, INPUT);
    
    // Configuración ADC
    analogReadResolution(12); // 12 bits de resolución
    analogSetAttenuation(ADC_11db); // Rango completo 0-3.3V
    
    Serial.println("Sistema de control integrado inicializado");
    Serial.println("Temperatura objetivo: " + String(temperatura_objetivo) + "°C");
}

void loop() {
    unsigned long tiempo_actual = millis();
    
    if (tiempo_actual - ultimo_muestreo >= INTERVALO_MUESTREO) {
        // Lectura de sensores
        DatosSensores datos = leerSensores();
        
        if (datos.sensor_valido) {
            // Control basado en temperatura
            ejecutarControlTemperatura(datos);
            
            // Control manual por potenciómetro
            ejecutarControlManual(datos);
            
            // Monitoreo y logging
            mostrarEstadoSistema(datos);
        }
        
        ultimo_muestreo = tiempo_actual;
    }
}

DatosSensores leerSensores() {
    DatosSensores datos;
    
    // Lectura del sensor DHT22
    datos.temperatura = dht.readTemperature();
    datos.humedad = dht.readHumidity();
    
    // Lectura del potenciómetro con filtrado
    int suma_lecturas = 0;
    for (int i = 0; i < 10; i++) {
        suma_lecturas += analogRead(POTENTIOMETER_PIN);
        delay(1);
    }
    datos.potenciometro = suma_lecturas / 10; // Promedio de 10 lecturas
    
    // Validación de datos
    datos.sensor_valido = !isnan(datos.temperatura) && !isnan(datos.humedad);
    
    return datos;
}

void ejecutarControlTemperatura(DatosSensores datos) {
    float error_temperatura = datos.temperatura - temperatura_objetivo;
    
    // Control proporcional simple
    if (error_temperatura > 2.0) {
        // Temperatura muy alta - activar enfriamiento
        intensidad_led = 255; // LED al máximo (simula ventilador)
        controlServo.write(180); // Servo abre válvula de ventilación
        
    } else if (error_temperatura < -2.0) {
        // Temperatura muy baja - reducir enfriamiento
        intensidad_led = 0;
        controlServo.write(0); // Servo cierra válvula
        
    } else {
        // Control proporcional en zona muerta
        intensidad_led = map(abs(error_temperatura * 100), 0, 200, 50, 200);
        int angulo_servo = map(error_temperatura * 10, -20, 20, 0, 180);
        angulo_servo = constrain(angulo_servo, 0, 180);
        controlServo.write(angulo_servo);
    }
    
    // Aplicar control PWM al LED
    analogWrite(LED_PIN, intensidad_led);
}

void ejecutarControlManual(DatosSensores datos) {
    // Control manual override por potenciómetro
    if (datos.potenciometro > 3000) { // Umbral de activación manual
        temperatura_objetivo = map(datos.potenciometro, 3000, 4095, 20, 35);
        Serial.println("Modo manual activo - Nueva temperatura objetivo: " + 
                      String(temperatura_objetivo) + "°C");
    }
}

void mostrarEstadoSistema(DatosSensores datos) {
    Serial.println("=== Estado del Sistema ===");
    Serial.println("Temperatura: " + String(datos.temperatura, 2) + "°C");
    Serial.println("Humedad: " + String(datos.humedad, 2) + "%");
    Serial.println("Potenciómetro: " + String(datos.potenciometro) + " (" + 
                   String(map(datos.potenciometro, 0, 4095, 0, 100)) + "%)");
    Serial.println("Intensidad LED: " + String(intensidad_led));
    Serial.println("Temperatura objetivo: " + String(temperatura_objetivo, 1) + "°C");
    Serial.println("Servo posición: " + String(controlServo.read()) + "°");
    Serial.println("Memoria libre: " + String(ESP.getFreeHeap()) + " bytes");
    Serial.println("------------------------");
}
Consideraciones de Filtrado Digital

El código implementa filtrado por promediado para el potenciómetro, reduciendo el ruido eléctrico. Para aplicaciones críticas, considere implementar filtros Kalman o filtros pasa-bajos digitales.

Ejercicios Prácticos de Integración

1

Adquisición Multi-Sensor con Filtrado

Básico 30 min DHT22 + LDR

Objetivo: Implementar un sistema de adquisición de datos con múltiples sensores aplicando técnicas de filtrado digital para mejorar la precisión de las mediciones.

Materiales Necesarios:
  • ESP32 DevKit
  • Sensor DHT22 (temperatura/humedad)
  • LDR (sensor de luz)
  • Resistencias 10kΩ y 220Ω
  • Protoboard y cables
Sistema Multi-Sensor
#include "DHT.h"
#include 

#define DHT_PIN 4
#define LDR_PIN 34
#define DHT_TYPE DHT22

DHT dht(DHT_PIN, DHT_TYPE);

// Buffer circular para filtrado
const int BUFFER_SIZE = 10;
struct SensorBuffer {
    float temperatura[BUFFER_SIZE];
    float humedad[BUFFER_SIZE];
    int luz[BUFFER_SIZE];
    int indice;
    bool lleno;
};

SensorBuffer buffer = {{0}, {0}, {0}, 0, false};

void setup() {
    Serial.begin(115200);
    dht.begin();
    
    // Configurar ADC para mayor precisión
    analogReadResolution(12);
    analogSetAttenuation(ADC_11db);
    
    Serial.println("Sistema multi-sensor iniciado");
}

void loop() {
    // Adquisición de datos raw
    float temp_raw = dht.readTemperature();
    float hum_raw = dht.readHumidity();
    int luz_raw = analogRead(LDR_PIN);
    
    if (!isnan(temp_raw) && !isnan(hum_raw)) {
        // Almacenar en buffer circular
        buffer.temperatura[buffer.indice] = temp_raw;
        buffer.humedad[buffer.indice] = hum_raw;
        buffer.luz[buffer.indice] = luz_raw;
        
        buffer.indice = (buffer.indice + 1) % BUFFER_SIZE;
        if (buffer.indice == 0) buffer.lleno = true;
        
        // Calcular valores filtrados
        DatosFilteredSensor datos = calcularPromedios();
        
        // Generar JSON con datos
        generarJSON(datos, temp_raw, hum_raw, luz_raw);
    }
    
    delay(1000);
}

struct DatosFilteredSensor {
    float temp_filtrada;
    float hum_filtrada;
    int luz_filtrada;
    float temp_desviacion;
    float hum_desviacion;
};

DatosFilteredSensor calcularPromedios() {
    DatosFilteredSensor datos;
    int elementos = buffer.lleno ? BUFFER_SIZE : buffer.indice;
    
    if (elementos == 0) return datos;
    
    // Calcular promedios
    float suma_temp = 0, suma_hum = 0;
    long suma_luz = 0;
    
    for (int i = 0; i < elementos; i++) {
        suma_temp += buffer.temperatura[i];
        suma_hum += buffer.humedad[i];
        suma_luz += buffer.luz[i];
    }
    
    datos.temp_filtrada = suma_temp / elementos;
    datos.hum_filtrada = suma_hum / elementos;
    datos.luz_filtrada = suma_luz / elementos;
    
    // Calcular desviación estándar
    float var_temp = 0, var_hum = 0;
    for (int i = 0; i < elementos; i++) {
        var_temp += pow(buffer.temperatura[i] - datos.temp_filtrada, 2);
        var_hum += pow(buffer.humedad[i] - datos.hum_filtrada, 2);
    }
    
    datos.temp_desviacion = sqrt(var_temp / elementos);
    datos.hum_desviacion = sqrt(var_hum / elementos);
    
    return datos;
}

void generarJSON(DatosFilteredSensor filtrados, float temp_raw, float hum_raw, int luz_raw) {
    StaticJsonDocument<500> doc;
    
    doc["timestamp"] = millis();
    doc["raw"]["temperatura"] = temp_raw;
    doc["raw"]["humedad"] = hum_raw;
    doc["raw"]["luz"] = luz_raw;
    
    doc["filtered"]["temperatura"] = filtrados.temp_filtrada;
    doc["filtered"]["humedad"] = filtrados.hum_filtrada;
    doc["filtered"]["luz"] = filtrados.luz_filtrada;
    
    doc["stats"]["temp_std"] = filtrados.temp_desviacion;
    doc["stats"]["hum_std"] = filtrados.hum_desviacion;
    doc["stats"]["memoria_libre"] = ESP.getFreeHeap();
    
    serializeJson(doc, Serial);
    Serial.println();
}
2

Control PID para Servo Motor

Intermedio 45 min Control PID

Objetivo: Implementar un controlador PID para posicionamiento preciso de servo motor con retroalimentación por potenciómetro.

Materiales Necesarios:
  • ESP32 DevKit
  • Servo motor SG90
  • Potenciómetro 10kΩ (retroalimentación)
  • Potenciómetro 10kΩ (setpoint)
  • LED RGB (indicador)
Control PID Servo
#include "ESP32Servo.h"

// Definición de pines
#define SERVO_PIN 18
#define FEEDBACK_PIN 34  // Potenciómetro de retroalimentación
#define SETPOINT_PIN 35  // Potenciómetro de referencia
#define LED_R_PIN 25
#define LED_G_PIN 26
#define LED_B_PIN 27

Servo servoMotor;

// Estructura del controlador PID
struct ControladorPID {
    double Kp, Ki, Kd;          // Constantes PID
    double error_anterior;       // Error en el ciclo anterior
    double integral;            // Acumulador integral
    double salida_min, salida_max; // Límites de salida
    unsigned long tiempo_anterior;
};

ControladorPID pid = {
    .Kp = 2.0,
    .Ki = 0.5,
    .Kd = 0.1,
    .error_anterior = 0,
    .integral = 0,
    .salida_min = 0,
    .salida_max = 180,
    .tiempo_anterior = 0
};

void setup() {
    Serial.begin(115200);
    
    // Configurar servo
    servoMotor.attach(SERVO_PIN);
    
    // Configurar pines LED
    pinMode(LED_R_PIN, OUTPUT);
    pinMode(LED_G_PIN, OUTPUT);
    pinMode(LED_B_PIN, OUTPUT);
    
    // Configurar ADC
    analogReadResolution(12);
    
    Serial.println("Controlador PID iniciado");
    Serial.println("Kp=" + String(pid.Kp) + " Ki=" + String(pid.Ki) + " Kd=" + String(pid.Kd));
}

void loop() {
    // Leer setpoint y feedback
    int setpoint_raw = analogRead(SETPOINT_PIN);
    int feedback_raw = analogRead(FEEDBACK_PIN);
    
    // Convertir a grados (0-180)
    double setpoint = map(setpoint_raw, 0, 4095, 0, 180);
    double feedback = map(feedback_raw, 0, 4095, 0, 180);
    
    // Ejecutar control PID
    double salida_pid = calcularPID(setpoint, feedback);
    
    // Aplicar salida al servo
    servoMotor.write((int)salida_pid);
    
    // Actualizar indicador LED
    actualizarLED(setpoint, feedback);
    
    // Monitoreo serial
    mostrarDatosPID(setpoint, feedback, salida_pid);
    
    delay(20); // 50 Hz de frecuencia de control
}

double calcularPID(double setpoint, double posicion_actual) {
    unsigned long tiempo_actual = millis();
    double dt = (tiempo_actual - pid.tiempo_anterior) / 1000.0; // Convertir a segundos
    
    if (dt <= 0) return servoMotor.read(); // Evitar división por cero
    
    // Calcular error
    double error = setpoint - posicion_actual;
    
    // Término proporcional
    double termino_P = pid.Kp * error;
    
    // Término integral con anti-windup
    pid.integral += error * dt;
    if (pid.integral > 100) pid.integral = 100;
    if (pid.integral < -100) pid.integral = -100;
    double termino_I = pid.Ki * pid.integral;
    
    // Término derivativo
    double derivada = (error - pid.error_anterior) / dt;
    double termino_D = pid.Kd * derivada;
    
    // Calcular salida total
    double salida = termino_P + termino_I + termino_D;
    
    // Aplicar límites
    if (salida > pid.salida_max) salida = pid.salida_max;
    if (salida < pid.salida_min) salida = pid.salida_min;
    
    // Actualizar variables para próximo ciclo
    pid.error_anterior = error;
    pid.tiempo_anterior = tiempo_actual;
    
    return salida;
}

void actualizarLED(double setpoint, double feedback) {
    double error_absoluto = abs(setpoint - feedback);
    
    if (error_absoluto < 2) {
        // Verde: posición correcta
        digitalWrite(LED_R_PIN, LOW);
        digitalWrite(LED_G_PIN, HIGH);
        digitalWrite(LED_B_PIN, LOW);
    } else if (error_absoluto < 10) {
        // Amarillo: error moderado
        digitalWrite(LED_R_PIN, HIGH);
        digitalWrite(LED_G_PIN, HIGH);
        digitalWrite(LED_B_PIN, LOW);
    } else {
        // Rojo: error grande
        digitalWrite(LED_R_PIN, HIGH);
        digitalWrite(LED_G_PIN, LOW);
        digitalWrite(LED_B_PIN, LOW);
    }
}

void mostrarDatosPID(double setpoint, double feedback, double salida) {
    static unsigned long ultimo_reporte = 0;
    
    if (millis() - ultimo_reporte > 500) { // Reportar cada 500ms
        Serial.println("=== Control PID ===");
        Serial.println("Setpoint: " + String(setpoint, 1) + "°");
        Serial.println("Feedback: " + String(feedback, 1) + "°");
        Serial.println("Salida: " + String(salida, 1) + "°");
        Serial.println("Error: " + String(setpoint - feedback, 1) + "°");
        Serial.println("Integral: " + String(pid.integral, 3));
        Serial.println("------------------");
        ultimo_reporte = millis();
    }
}
3

Máquina de Estados Compleja

Avanzado 60 min State Machine

Objetivo: Crear una máquina de estados finita para control de proceso automatizado con múltiples sensores y actuadores.

Escenario de Aplicación:

Sistema de control para proceso de llenado automático de contenedores con control de nivel, temperatura y válvulas.

Proyecto Final: Sistema de Control Industrial Completo

Sistema de Monitoreo y Control Ambiental Inteligente

Desarrolle un sistema completo de automatización industrial que integre múltiples sensores ambientales con control automatizado de actuadores, incluyendo comunicación IoT y interfaz de usuario web en tiempo real.

Características Principales:
  • Multi-sensor: DHT22, BME280, sensores de gas MQ-2/MQ-135
  • Control de actuadores: Ventiladores, válvulas, sistemas de iluminación
  • Comunicación: WiFi con MQTT y servidor web embebido
  • Interfaz: Dashboard web responsive con gráficos en tiempo real
  • Seguridad: Sistema de alarmas y notificaciones
  • Logging: Almacenamiento en SPIFFS y envío a la nube
  • Control: Algoritmos PID y máquinas de estado
  • Mantenimiento: Diagnóstico automático y OTA updates
Lista de Materiales
  • ESP32 DevKit v1
  • Sensor DHT22
  • Sensor BME280
  • Sensores MQ-2 y MQ-135
  • Ventiladores DC 12V (x2)
  • Módulo relé 8 canales
  • Display OLED 128x64
  • Fuente 12V/5A
  • Módulo MicroSD
Aplicación Industrial Real

Este proyecto integra conceptos avanzados de control, comunicación IoT e interfaces de usuario, preparándolo para implementar soluciones profesionales en entornos industriales.

Evaluación y Resolución de Problemas

Problemas Comunes y Soluciones
1. Lecturas Inconsistentes de Sensores
  • Causa: Ruido eléctrico o conexiones defectuosas
  • Solución: Implementar filtrado digital y verificar cableado
  • Código: Usar promedios móviles o filtros Kalman
2. Respuesta Lenta del Sistema
  • Causa: Bucles de delay excesivos o procesamiento bloqueante
  • Solución: Implementar programación no bloqueante
  • Herramienta: millis() en lugar de delay()
3. Inestabilidad en Control PID
  • Causa: Parámetros mal ajustados o saturación integral
  • Solución: Tuning sistemático e implementar anti-windup
  • Método: Comenzar con Kp bajo, añadir Ki y Kd gradualmente
Criterios de Evaluación
Aspectos Técnicos (40%)
  • Precisión en lectura de sensores (±2% de error máximo)
  • Tiempo de respuesta del sistema (<100ms para actuadores críticos)
  • Estabilidad del control (sin oscilaciones en régimen permanente)
Implementación de Código (35%)
  • Estructura modular y comentarios descriptivos
  • Manejo robusto de errores y excepciones
  • Optimización de memoria y procesamiento
Funcionalidad del Sistema (25%)
  • Integración correcta sensor-actuador
  • Interfaz de usuario intuitiva
  • Sistema de alarmas y seguridad funcional

Referencias y Recursos Avanzados