Introducción al Control PID
El Control PID (Proporcional, Integral, Derivativo) es una técnica matemática fundamental utilizada en sistemas de control para minimizar errores, proporcionando un ajuste preciso y estable en la respuesta del sistema. Este controlador se aplica extensivamente en la regulación de velocidad en motores de corriente continua (DC), que son componentes esenciales en la mecatrónica e IoT.
Aplicaciones Industriales
- Drones y vehículos aéreos no tripulados
- Vehículos eléctricos autónomos
- Sistemas de fabricación automatizada
- Robótica industrial avanzada
Ventajas del ESP32
- Microcontrolador de bajo costo
- Capacidades WiFi y Bluetooth integradas
- Procesador dual-core de 32 bits
- Múltiples canales PWM disponibles
Importancia en la Industria
En la industria, la velocidad de los motores DC se regula para mantener la eficiencia operativa de las máquinas y reducir su desgaste. En sistemas de transporte automatizado, un control preciso de velocidad es crucial para mantener la seguridad y eficiencia operacional.
Fundamentos Teóricos del Control PID
El control PID es un algoritmo de control en lazo cerrado que calcula un valor de error como la diferencia entre el valor deseado (setpoint) y el valor medido del proceso.
Proporcional (P)
Función: Produce una salida proporcional al error actual.
Efecto: Reduce el tiempo de subida pero puede causar sobrepaso.
P = Kp × error
Integral (I)
Función: Suma todos los errores pasados a lo largo del tiempo.
Efecto: Elimina el error en estado estable.
I = Ki × ∫error dt
Derivativo (D)
Función: Predice errores futuros basándose en la tasa de cambio.
Efecto: Reduce el sobrepaso y mejora la estabilidad.
D = Kd × d(error)/dt
Ecuación PID Completa
Salida = Kp × error + Ki × ∫error dt + Kd × d(error)/dt
Arquitectura ESP32 para Control de Motores
El ESP32 presenta características únicas que lo hacen ideal para aplicaciones de control de motores con retroalimentación PID.
Características del Hardware
- Dual-Core: Una CPU maneja WiFi/Bluetooth, otra el control
- PWM: 16 canales independientes para control preciso
- ADC: 18 canales de 12 bits para medición de sensores
- Timers: Control temporal preciso para bucles PID
Configuración de Pines
Pin | Función | Uso en PID |
---|---|---|
GPIO 25 | PWM Output | Control de motor |
GPIO 34 | ADC Input | Sensor velocidad |
GPIO 26-27 | Digital I/O | Dirección motor |
GPIO 32-33 | ADC/Touch | Encoder feedback |
Implementación del Control PID
A continuación se presenta la implementación completa de un controlador PID para motor DC con ESP32.
#include <PID_v1.h>
// Definir pines
const int MOTOR_PWM = 25;
const int MOTOR_DIR1 = 26;
const int MOTOR_DIR2 = 27;
const int ENCODER_A = 34;
const int ENCODER_B = 35;
// Variables PID
double setpoint = 100; // Velocidad deseada (RPM)
double input = 0; // Velocidad actual
double output = 0; // Salida PWM
// Parámetros PID - ajustables según el sistema
double Kp = 2.0, Ki = 5.0, Kd = 1.0;
// Crear objeto PID
PID motorPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
// Variables para encoder
volatile long encoderCount = 0;
long lastCount = 0;
unsigned long lastTime = 0;
const int PPR = 360; // Pulsos por revolución del encoder
void setup() {
Serial.begin(115200);
// Configurar pines
pinMode(MOTOR_PWM, OUTPUT);
pinMode(MOTOR_DIR1, OUTPUT);
pinMode(MOTOR_DIR2, OUTPUT);
pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
// Configurar interrupciones del encoder
attachInterrupt(digitalPinToInterrupt(ENCODER_A), readEncoder, CHANGE);
// Configurar PID
motorPID.SetMode(AUTOMATIC);
motorPID.SetOutputLimits(-255, 255);
motorPID.SetSampleTime(100); // 100ms
Serial.println("Control PID Motor DC iniciado");
}
void loop() {
// Calcular velocidad actual (RPM)
calculateSpeed();
// Ejecutar PID
motorPID.Compute();
// Aplicar salida al motor
controlMotor(output);
// Mostrar datos cada segundo
static unsigned long lastPrint = 0;
if (millis() - lastPrint >= 1000) {
Serial.printf("Setpoint: %.2f | Velocidad: %.2f | Salida: %.2f\n",
setpoint, input, output);
lastPrint = millis();
}
delay(10);
}
void readEncoder() {
// Leer estado del encoder
bool A = digitalRead(ENCODER_A);
bool B = digitalRead(ENCODER_B);
// Determinar dirección y contar
if (A == B) {
encoderCount++;
} else {
encoderCount--;
}
}
void calculateSpeed() {
unsigned long currentTime = millis();
long currentCount = encoderCount;
if (currentTime - lastTime >= 100) { // Actualizar cada 100ms
long deltaCount = currentCount - lastCount;
unsigned long deltaTime = currentTime - lastTime;
// Convertir a RPM
input = (deltaCount * 60000.0) / (PPR * deltaTime);
lastCount = currentCount;
lastTime = currentTime;
}
}
void controlMotor(double pwmValue) {
// Determinar dirección
if (pwmValue > 0) {
digitalWrite(MOTOR_DIR1, HIGH);
digitalWrite(MOTOR_DIR2, LOW);
analogWrite(MOTOR_PWM, abs(pwmValue));
} else if (pwmValue < 0) {
digitalWrite(MOTOR_DIR1, LOW);
digitalWrite(MOTOR_DIR2, HIGH);
analogWrite(MOTOR_PWM, abs(pwmValue));
} else {
digitalWrite(MOTOR_DIR1, LOW);
digitalWrite(MOTOR_DIR2, LOW);
analogWrite(MOTOR_PWM, 0);
}
}
#include <PID_v1.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
// Configuración WiFi
const char* ssid = "TU_WIFI";
const char* password = "TU_PASSWORD";
WebServer server(80);
// PID adaptativo con múltiples conjuntos de parámetros
struct PIDParams {
double kp, ki, kd;
String description;
};
PIDParams pidConfigs[] = {
{2.0, 5.0, 1.0, "Respuesta rápida"},
{1.5, 3.0, 0.5, "Estabilidad alta"},
{3.0, 8.0, 2.0, "Precisión máxima"}
};
int currentConfig = 0;
PID motorPID(&input, &output, &setpoint,
pidConfigs[currentConfig].kp,
pidConfigs[currentConfig].ki,
pidConfigs[currentConfig].kd, DIRECT);
// Variables de rendimiento
struct PerformanceMetrics {
double riseTime = 0;
double settlingTime = 0;
double overshoot = 0;
double steadyStateError = 0;
unsigned long testStartTime = 0;
bool testInProgress = false;
};
PerformanceMetrics metrics;
void setup() {
Serial.begin(115200);
// Configuración de hardware (igual que antes)
setupHardware();
// Conectar WiFi
connectWiFi();
// Configurar servidor web
setupWebServer();
// Configurar PID inicial
motorPID.SetMode(AUTOMATIC);
motorPID.SetOutputLimits(-255, 255);
motorPID.SetSampleTime(50);
Serial.println("Sistema PID Avanzado iniciado");
}
void setupWebServer() {
// API para cambiar setpoint
server.on("/api/setpoint", HTTP_POST, []() {
if (server.hasArg("value")) {
setpoint = server.arg("value").toDouble();
server.send(200, "application/json", "{\"status\":\"ok\"}");
} else {
server.send(400, "application/json", "{\"error\":\"missing value\"}");
}
});
// API para cambiar configuración PID
server.on("/api/pid-config", HTTP_POST, []() {
if (server.hasArg("config")) {
int config = server.arg("config").toInt();
if (config >= 0 && config < 3) {
changePIDConfig(config);
server.send(200, "application/json", "{\"status\":\"ok\"}");
} else {
server.send(400, "application/json", "{\"error\":\"invalid config\"}");
}
}
});
// API para obtener métricas
server.on("/api/metrics", HTTP_GET, []() {
DynamicJsonDocument doc(1024);
doc["setpoint"] = setpoint;
doc["current_speed"] = input;
doc["output"] = output;
doc["rise_time"] = metrics.riseTime;
doc["settling_time"] = metrics.settlingTime;
doc["overshoot"] = metrics.overshoot;
doc["steady_state_error"] = metrics.steadyStateError;
doc["current_config"] = pidConfigs[currentConfig].description;
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
});
server.begin();
}
void loop() {
server.handleClient();
// Calcular velocidad
calculateSpeed();
// Ejecutar PID
motorPID.Compute();
// Aplicar control
controlMotor(output);
// Analizar rendimiento
analyzePerformance();
// Auto-tuning si es necesario
if (metrics.steadyStateError > 10.0 && !metrics.testInProgress) {
autoTunePID();
}
delay(10);
}
void changePIDConfig(int config) {
currentConfig = config;
motorPID.SetTunings(pidConfigs[config].kp,
pidConfigs[config].ki,
pidConfigs[config].kd);
Serial.printf("PID configurado: %s\n", pidConfigs[config].description.c_str());
}
void analyzePerformance() {
static double previousSetpoint = setpoint;
static double maxValue = 0;
static bool riseTimeCalculated = false;
// Detectar cambio de setpoint
if (abs(setpoint - previousSetpoint) > 1.0) {
metrics.testStartTime = millis();
metrics.testInProgress = true;
maxValue = 0;
riseTimeCalculated = false;
previousSetpoint = setpoint;
}
if (metrics.testInProgress) {
unsigned long elapsed = millis() - metrics.testStartTime;
// Calcular tiempo de subida (10% a 90% del setpoint)
if (!riseTimeCalculated && input >= 0.9 * setpoint) {
metrics.riseTime = elapsed / 1000.0;
riseTimeCalculated = true;
}
// Seguir el valor máximo para calcular sobrepaso
if (input > maxValue) {
maxValue = input;
}
// Calcular tiempo de establecimiento (dentro del 5% del setpoint)
if (abs(input - setpoint) <= 0.05 * setpoint && elapsed > 2000) {
if (metrics.settlingTime == 0) {
metrics.settlingTime = elapsed / 1000.0;
metrics.overshoot = ((maxValue - setpoint) / setpoint) * 100.0;
metrics.testInProgress = false;
}
}
// Calcular error en estado estable
if (elapsed > 5000) {
metrics.steadyStateError = abs(input - setpoint);
}
}
}
void autoTunePID() {
// Implementación simplificada de auto-tuning Ziegler-Nichols
Serial.println("Iniciando auto-tuning PID...");
// Encontrar la mejor configuración basada en el error actual
double bestError = metrics.steadyStateError;
int bestConfig = currentConfig;
for (int i = 0; i < 3; i++) {
if (i != currentConfig) {
changePIDConfig(i);
delay(3000); // Esperar estabilización
if (metrics.steadyStateError < bestError) {
bestError = metrics.steadyStateError;
bestConfig = i;
}
}
}
if (bestConfig != currentConfig) {
changePIDConfig(bestConfig);
Serial.printf("Auto-tuning completado. Mejor configuración: %s\n",
pidConfigs[bestConfig].description.c_str());
}
}
Ejercicios Prácticos
Objetivo: Implementar control de velocidad básico usando potenciómetro como referencia de velocidad.
Materiales Necesarios:
- ESP32 DevKit
- Motor DC 12V con encoder
- Driver L298N
- Potenciómetro 10kΩ
- Fuente 12V / 2A
Tareas:
- Conectar circuito según esquema
- Implementar lectura de potenciómetro
- Configurar control PWM básico
- Probar respuesta del sistema
- Documentar comportamiento
Objetivo: Desarrollar un sistema completo de control PID con retroalimentación de encoder y interfaz serial.
Funcionalidades:
- Control PID con parámetros ajustables
- Medición precisa de velocidad con encoder
- Interfaz serial para monitoreo
- Gráficas en tiempo real
- Logging de datos
Métricas de Evaluación:
- Tiempo de subida < 2 segundos
- Sobrepaso < 10%
- Error estado estable < 2%
- Tiempo establecimiento < 5 segundos
Objetivo: Implementar algoritmos de auto-tuning PID y crear interfaz web para monitoreo remoto.
Algoritmos Implementados:
- Ziegler-Nichols automático
- Cohen-Coon adaptativo
- Optimización por enjambre
- Machine Learning básico
Características IoT:
- Dashboard web responsivo
- API REST completa
- Almacenamiento en la nube
- Alertas por WhatsApp/Telegram
- Control remoto via smartphone
Proyecto Aplicado: Sistema de Control PID para Robot Móvil
Desarrollaremos un sistema completo de control PID aplicado a un robot móvil con capacidades de seguimiento de trayectoria y navegación autónoma.
Especificaciones del Proyecto
-
Plataforma: ESP32 con dual-core para control paralelo
Un core para PID, otro para comunicaciones -
Motores: 2x motores DC con encoders ópticos de 360 PPR
Control independiente de velocidad para cada rueda -
Sensores: Cámara ESP32-CAM + IMU MPU6050 + Ultrasonidos
Visión computacional y navegación por sensores -
Conectividad: WiFi para control remoto y telemetría
Dashboard web y API REST para monitoreo
Lista de Materiales
ESP32 DevKit | $12 |
2x Motor+Encoder | $25 |
Driver L298N | $8 |
IMU MPU6050 | $5 |
Ultrasónico HC-SR04 | $3 |
Chasis y ruedas | $15 |
Batería LiPo | $20 |
Total | $88 |
Fase 1: Hardware
- Ensamble del chasis
- Instalación de motores
- Conexión de encoders
- Integración de sensores
- Sistema de alimentación
Fase 2: Software
- Control PID para cada motor
- Fusión de sensores (Kalman)
- Algoritmos de navegación
- Interfaz web de control
- Sistema de telemetría
Fase 3: Optimización
- Tuning automático PID
- Calibración de sensores
- Pruebas de rendimiento
- Optimización de código
- Documentación final
Troubleshooting y Optimización
Problemas Comunes
Síntomas: El motor oscila alrededor del setpoint sin estabilizarse.
Causas:
- Kp demasiado alto
- Kd insuficiente
- Ruido en las mediciones
- Tiempo de muestreo muy alto
Soluciones:
- Reducir Kp gradualmente
- Aumentar Kd para mayor amortiguamiento
- Implementar filtro paso bajo
- Reducir tiempo de muestreo a 20-50ms
Síntomas: El sistema no alcanza exactamente el setpoint.
Causas:
- Ki muy bajo o cero
- Saturación del integrador
- Zona muerta en el actuador
- Fricción estática alta
Soluciones:
- Aumentar Ki gradualmente
- Implementar anti-windup
- Compensar zona muerta
- Usar feed-forward para fricción
Síntomas: El sistema tarda mucho en alcanzar el setpoint.
Causas:
- Kp muy bajo
- Límites de salida restrictivos
- Tiempo de muestreo muy alto
- Inercia del sistema alta
Soluciones:
- Aumentar Kp progresivamente
- Verificar límites PWM (0-255)
- Reducir tiempo de muestreo
- Implementar pre-filtro setpoint
Métricas de Evaluación
Criterios de Rendimiento
Métrica | Objetivo | Excelente |
---|---|---|
Tiempo de Subida | < 3s | < 1s |
Sobrepaso | < 15% | < 5% |
Tiempo de Establecimiento | < 8s | < 3s |
Error Estado Estable | < 5% | < 1% |
Rechazo Perturbaciones | < 10% | < 3% |
Tips de Optimización
- Usar EEPROM para guardar parámetros PID
- Implementar modo manual/automático
- Añadir límites de velocidad de cambio
- Usar filtros digitales para ruido
- Monitorear temperatura del motor
- Implementar protección por sobrecorriente