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 → SensoresArquitectura 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:
#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
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
#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();
}
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)
#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();
}
}
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 dedelay()
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