Módulo 4

I2C: sensores MPU6050, pantallas OLED

Periféricos Digitales

ESP32 Mecatrónica IoT UNAM

Fundamentos del Protocolo I2C en ESP32

El protocolo I2C (Inter-Integrated Circuit), también conocido como TWI (Two-Wire Interface), es un protocolo de comunicación serie síncrono diseñado por Philips en 1982. Este protocolo permite la comunicación entre múltiples dispositivos utilizando únicamente dos líneas: SDA (Serial Data) para datos y SCL (Serial Clock) para el reloj.

En aplicaciones industriales de mecatrónica, el I2C es fundamental para integrar múltiples sensores, actuadores y dispositivos de visualización en un sistema coherente. Su arquitectura maestro-esclavo permite que un microcontrolador ESP32 gestione hasta 127 dispositivos en el mismo bus, cada uno identificado por una dirección única de 7 bits.

El MPU6050 es una Unidad de Medición Inercial (IMU) de 6 grados de libertad que integra un acelerómetro de 3 ejes y un giroscopio de 3 ejes. Este sensor es esencial en aplicaciones como:

  • Sistemas de estabilización en drones y gimbals
  • Navegación inercial en robots móviles
  • Detección de gestos en interfaces hombre-máquina
  • Monitoreo de vibraciones en maquinaria industrial

Las pantallas OLED (Organic Light-Emitting Diode) ofrecen ventajas significativas sobre las LCD tradicionales: mayor contraste, menor consumo energético, tiempos de respuesta más rápidos y capacidad de operación en temperaturas extremas, ideales para entornos industriales.

Especificaciones Técnicas I2C en ESP32

Características Hardware
  • Dos controladores I2C independientes
  • Velocidad máxima: 1 MHz (Fast Mode Plus)
  • Modos soportados: Standard (100 kHz), Fast (400 kHz)
  • Resistencias pull-up internas configurables
  • Detección automática de errores y arbitraje
Pines por Defecto
  • I2C0: SDA=GPIO21, SCL=GPIO22
  • I2C1: SDA=GPIO33, SCL=GPIO32
  • Pines configurables por software
  • Soporte para multiplexado de pines

Implementación Técnica Completa

La implementación profesional del I2C en ESP32 requiere consideraciones avanzadas de configuración, manejo de errores y optimización de rendimiento.

Sistema Completo de Lectura MPU6050
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// Configuraciones I2C y Hardware
#define I2C_SDA 21
#define I2C_SCL 22
#define I2C_FREQ 400000  // 400 kHz Fast Mode

// Configuraciones MPU6050
#define MPU6050_ADDR 0x68
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_CONFIG 0x1A
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_GYRO_CONFIG 0x1B

// Configuraciones OLED
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C

// Configuraciones del Sistema
#define SAMPLE_RATE 100  // Hz
#define CALIBRATION_SAMPLES 1000
#define DATA_BUFFER_SIZE 256
#define EEPROM_SIZE 512

// Estructura de datos del sensor
struct SensorData {
    float accel_x, accel_y, accel_z;  // g
    float gyro_x, gyro_y, gyro_z;    // deg/s
    float temperature;                // °C
    float pitch, roll, yaw;           // deg
    unsigned long timestamp;          // ms
};

// Estructura de calibración
struct CalibrationData {
    float accel_offset[3];
    float gyro_offset[3];
    bool is_calibrated;
    uint32_t checksum;
};

class IMUProcessor {
private:
    Adafruit_SSD1306 display;
    WebServer server;
    
    SensorData current_data;
    CalibrationData calibration;
    SensorData data_buffer[DATA_BUFFER_SIZE];
    uint16_t buffer_index = 0;
    
    // Filtros y procesamiento
    float alpha = 0.96;  // Filtro complementario
    unsigned long last_update = 0;
    
    // Sistema de estadísticas
    struct Statistics {
        float min_vals[6], max_vals[6];
        float rms_vals[6];
        uint32_t sample_count;
        bool alerts_active[6];
    } stats;
    
    // Configuración WiFi
    const char* ssid = "ESP32_IMU_System";
    const char* password = "Industrial2024";

public:
    IMUProcessor() : display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET), server(80) {}
    
    bool initialize() {
        Serial.begin(115200);
        EEPROM.begin(EEPROM_SIZE);
        
        // Inicialización I2C con configuración avanzada
        Wire.begin(I2C_SDA, I2C_SCL);
        Wire.setClock(I2C_FREQ);
        Wire.setTimeOut(5000);  // Timeout 5ms
        
        // Verificar conexión MPU6050
        if (!testConnection()) {
            Serial.println("Error: MPU6050 no detectado");
            return false;
        }
        
        // Inicializar MPU6050
        if (!initializeMPU6050()) {
            Serial.println("Error: Fallo en inicialización MPU6050");
            return false;
        }
        
        // Inicializar OLED
        if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
            Serial.println("Error: Fallo en inicialización OLED");
            return false;
        }
        
        display.clearDisplay();
        display.setTextColor(SSD1306_WHITE);
        display.setTextSize(1);
        display.setCursor(0, 0);
        display.println("IMU System v2.0");
        display.println("Inicializando...");
        display.display();
        
        // Cargar calibración desde EEPROM
        loadCalibration();
        
        // Inicializar WiFi
        setupWiFi();
        
        // Configurar servidor web
        setupWebServer();
        
        // Inicializar estadísticas
        resetStatistics();
        
        Serial.println("Sistema IMU inicializado correctamente");
        return true;
    }
    
private:
    bool testConnection() {
        Wire.beginTransmission(MPU6050_ADDR);
        return (Wire.endTransmission() == 0);
    }
    
    bool initializeMPU6050() {
        // Despertar MPU6050
        writeRegister(MPU6050_PWR_MGMT_1, 0x00);
        delay(100);
        
        // Configurar filtro paso bajo (DLPF = 3, ~44Hz)
        writeRegister(MPU6050_CONFIG, 0x03);
        
        // Configurar rango acelerómetro (±8g)
        writeRegister(MPU6050_ACCEL_CONFIG, 0x10);
        
        // Configurar rango giroscopio (±1000°/s)
        writeRegister(MPU6050_GYRO_CONFIG, 0x10);
        
        // Configurar frecuencia de muestreo
        writeRegister(0x19, 79);  // Sample rate = 100Hz
        
        delay(100);
        
        // Verificar configuración
        uint8_t config = readRegister(MPU6050_CONFIG);
        uint8_t accel_config = readRegister(MPU6050_ACCEL_CONFIG);
        uint8_t gyro_config = readRegister(MPU6050_GYRO_CONFIG);
        
        Serial.printf("MPU6050 Config: 0x%02X, Accel: 0x%02X, Gyro: 0x%02X\n", 
                     config, accel_config, gyro_config);
        
        return true;
    }
    
    void writeRegister(uint8_t reg, uint8_t value) {
        Wire.beginTransmission(MPU6050_ADDR);
        Wire.write(reg);
        Wire.write(value);
        Wire.endTransmission(true);
    }
    
    uint8_t readRegister(uint8_t reg) {
        Wire.beginTransmission(MPU6050_ADDR);
        Wire.write(reg);
        Wire.endTransmission(false);
        Wire.requestFrom(MPU6050_ADDR, 1, true);
        return Wire.read();
    }
    
public:
    void calibrateIMU() {
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println("Calibrando IMU...");
        display.println("Mantener quieto");
        display.display();
        
        Serial.println("Iniciando calibración...");
        
        // Reset calibration
        memset(&calibration, 0, sizeof(CalibrationData));
        
        float accel_sum[3] = {0, 0, 0};
        float gyro_sum[3] = {0, 0, 0};
        
        for (int i = 0; i < CALIBRATION_SAMPLES; i++) {
            SensorData raw_data;
            readRawData(&raw_data);
            
            accel_sum[0] += raw_data.accel_x;
            accel_sum[1] += raw_data.accel_y;
            accel_sum[2] += raw_data.accel_z - 1.0;  // Compensar gravedad
            
            gyro_sum[0] += raw_data.gyro_x;
            gyro_sum[1] += raw_data.gyro_y;
            gyro_sum[2] += raw_data.gyro_z;
            
            if (i % 100 == 0) {
                display.clearDisplay();
                display.setCursor(0, 0);
                display.printf("Calibrando: %d%%", (i * 100) / CALIBRATION_SAMPLES);
                display.display();
                Serial.printf("Progreso calibración: %d%%\n", (i * 100) / CALIBRATION_SAMPLES);
            }
            
            delay(1);
        }
        
        // Calcular offsets
        for (int i = 0; i < 3; i++) {
            calibration.accel_offset[i] = accel_sum[i] / CALIBRATION_SAMPLES;
            calibration.gyro_offset[i] = gyro_sum[i] / CALIBRATION_SAMPLES;
        }
        
        calibration.is_calibrated = true;
        calibration.checksum = calculateChecksum();
        
        // Guardar en EEPROM
        saveCalibration();
        
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println("Calibracion OK");
        display.printf("Ax:%.3f Ay:%.3f", calibration.accel_offset[0], calibration.accel_offset[1]);
        display.printf("\nGx:%.2f Gy:%.2f", calibration.gyro_offset[0], calibration.gyro_offset[1]);
        display.display();
        
        Serial.println("Calibración completada y guardada");
        delay(3000);
    }
    
private:
    void readRawData(SensorData* data) {
        Wire.beginTransmission(MPU6050_ADDR);
        Wire.write(MPU6050_ACCEL_XOUT_H);
        Wire.endTransmission(false);
        Wire.requestFrom(MPU6050_ADDR, 14, true);
        
        // Leer acelerómetro (6 bytes)
        int16_t raw_accel[3];
        raw_accel[0] = (Wire.read() << 8) | Wire.read();
        raw_accel[1] = (Wire.read() << 8) | Wire.read();
        raw_accel[2] = (Wire.read() << 8) | Wire.read();
        
        // Leer temperatura (2 bytes)
        int16_t raw_temp = (Wire.read() << 8) | Wire.read();
        
        // Leer giroscopio (6 bytes)
        int16_t raw_gyro[3];
        raw_gyro[0] = (Wire.read() << 8) | Wire.read();
        raw_gyro[1] = (Wire.read() << 8) | Wire.read();
        raw_gyro[2] = (Wire.read() << 8) | Wire.read();
        
        // Convertir a unidades físicas
        data->accel_x = raw_accel[0] / 4096.0;  // ±8g range
        data->accel_y = raw_accel[1] / 4096.0;
        data->accel_z = raw_accel[2] / 4096.0;
        
        data->gyro_x = raw_gyro[0] / 32.8;      // ±1000°/s range
        data->gyro_y = raw_gyro[1] / 32.8;
        data->gyro_z = raw_gyro[2] / 32.8;
        
        data->temperature = (raw_temp / 340.0) + 36.53;
        data->timestamp = millis();
    }
    
public:
    void updateSensorData() {
        readRawData(¤t_data);
        
        // Aplicar calibración
        if (calibration.is_calibrated) {
            current_data.accel_x -= calibration.accel_offset[0];
            current_data.accel_y -= calibration.accel_offset[1];
            current_data.accel_z -= calibration.accel_offset[2];
            
            current_data.gyro_x -= calibration.gyro_offset[0];
            current_data.gyro_y -= calibration.gyro_offset[1];
            current_data.gyro_z -= calibration.gyro_offset[2];
        }
        
        // Calcular ángulos usando filtro complementario
        calculateOrientation();
        
        // Actualizar buffer de datos
        data_buffer[buffer_index] = current_data;
        buffer_index = (buffer_index + 1) % DATA_BUFFER_SIZE;
        
        // Actualizar estadísticas
        updateStatistics();
    }
    
private:
    void calculateOrientation() {
        float dt = (current_data.timestamp - last_update) / 1000.0;
        last_update = current_data.timestamp;
        
        if (dt > 0.1) dt = 0.01;  // Limitar dt máximo
        
        // Calcular ángulos desde acelerómetro
        float accel_pitch = atan2(current_data.accel_y, 
                                 sqrt(current_data.accel_x * current_data.accel_x + 
                                      current_data.accel_z * current_data.accel_z)) * 180.0 / PI;
        float accel_roll = atan2(-current_data.accel_x, current_data.accel_z) * 180.0 / PI;
        
        // Integrar giroscopio
        current_data.pitch += current_data.gyro_x * dt;
        current_data.roll += current_data.gyro_y * dt;
        current_data.yaw += current_data.gyro_z * dt;
        
        // Aplicar filtro complementario
        current_data.pitch = alpha * current_data.pitch + (1 - alpha) * accel_pitch;
        current_data.roll = alpha * current_data.roll + (1 - alpha) * accel_roll;
        
        // Limitar yaw a ±180°
        if (current_data.yaw > 180) current_data.yaw -= 360;
        if (current_data.yaw < -180) current_data.yaw += 360;
    }
    
public:
    void updateDisplay() {
        display.clearDisplay();
        
        // Título
        display.setTextSize(1);
        display.setCursor(0, 0);
        display.printf("IMU Monitor %.1f°C", current_data.temperature);
        
        // Línea separadora
        display.drawLine(0, 10, 127, 10, SSD1306_WHITE);
        
        // Acelerómetro
        display.setCursor(0, 14);
        display.setTextSize(1);
        display.printf("Ax:%+.2fg", current_data.accel_x);
        display.setCursor(70, 14);
        display.printf("Ay:%+.2fg", current_data.accel_y);
        display.setCursor(0, 24);
        display.printf("Az:%+.2fg", current_data.accel_z);
        
        // Giroscopio
        display.setCursor(70, 24);
        display.printf("Gx:%+4.0f°/s", current_data.gyro_x);
        display.setCursor(0, 34);
        display.printf("Gy:%+4.0f°/s", current_data.gyro_y);
        display.setCursor(70, 34);
        display.printf("Gz:%+4.0f°/s", current_data.gyro_z);
        
        // Orientación
        display.drawLine(0, 44, 127, 44, SSD1306_WHITE);
        display.setCursor(0, 48);
        display.printf("P:%+5.1f° R:%+5.1f°", current_data.pitch, current_data.roll);
        display.setCursor(0, 58);
        display.printf("Yaw:%+6.1f°", current_data.yaw);
        
        // Indicadores de estado
        if (calibration.is_calibrated) {
            display.setCursor(110, 58);
            display.print("CAL");
        }
        
        display.display();
    }
    
    void processLoop() {
        static unsigned long last_sensor_update = 0;
        static unsigned long last_display_update = 0;
        
        unsigned long now = millis();
        
        // Actualizar sensores a 100Hz
        if (now - last_sensor_update >= 10) {
            updateSensorData();
            last_sensor_update = now;
        }
        
        // Actualizar display a 10Hz
        if (now - last_display_update >= 100) {
            updateDisplay();
            last_display_update = now;
        }
        
        // Procesar servidor web
        server.handleClient();
        
        // Verificar comandos serie
        processSerialCommands();
    }
    
private:
    void setupWiFi() {
        WiFi.softAP(ssid, password);
        Serial.println("WiFi AP iniciado:");
        Serial.print("SSID: ");
        Serial.println(ssid);
        Serial.print("IP: ");
        Serial.println(WiFi.softAPIP());
    }
    
    void setupWebServer() {
        server.on("/", HTTP_GET, [this]() {
            String html = generateWebInterface();
            server.send(200, "text/html", html);
        });
        
        server.on("/data", HTTP_GET, [this]() {
            DynamicJsonDocument doc(1024);
            doc["accel"]["x"] = current_data.accel_x;
            doc["accel"]["y"] = current_data.accel_y;
            doc["accel"]["z"] = current_data.accel_z;
            doc["gyro"]["x"] = current_data.gyro_x;
            doc["gyro"]["y"] = current_data.gyro_y;
            doc["gyro"]["z"] = current_data.gyro_z;
            doc["orientation"]["pitch"] = current_data.pitch;
            doc["orientation"]["roll"] = current_data.roll;
            doc["orientation"]["yaw"] = current_data.yaw;
            doc["temperature"] = current_data.temperature;
            doc["timestamp"] = current_data.timestamp;
            doc["calibrated"] = calibration.is_calibrated;
            
            String response;
            serializeJson(doc, response);
            server.send(200, "application/json", response);
        });
        
        server.on("/calibrate", HTTP_POST, [this]() {
            calibrateIMU();
            server.send(200, "text/plain", "Calibración completada");
        });
        
        server.begin();
    }
    
    String generateWebInterface() {
        return R"(


    IMU Monitor ESP32
    


    

Monitor IMU ESP32

Acelerómetro X
0.00 g
Acelerómetro Y
0.00 g
Acelerómetro Z
0.00 g
Giroscopio X
0.0 °/s
Giroscopio Y
0.0 °/s
Giroscopio Z
0.0 °/s
Pitch
0.0°
Roll
0.0°
Yaw
0.0°
Temperatura
0.0°C
)"; } void updateStatistics() { float values[6] = { current_data.accel_x, current_data.accel_y, current_data.accel_z, current_data.gyro_x, current_data.gyro_y, current_data.gyro_z }; for (int i = 0; i < 6; i++) { if (stats.sample_count == 0) { stats.min_vals[i] = stats.max_vals[i] = values[i]; stats.rms_vals[i] = 0; } else { stats.min_vals[i] = min(stats.min_vals[i], values[i]); stats.max_vals[i] = max(stats.max_vals[i], values[i]); stats.rms_vals[i] = sqrt((stats.rms_vals[i] * stats.rms_vals[i] * stats.sample_count + values[i] * values[i]) / (stats.sample_count + 1)); } } stats.sample_count++; // Reset cada hora if (stats.sample_count > 360000) { // 100Hz * 3600s resetStatistics(); } } void resetStatistics() { memset(&stats, 0, sizeof(Statistics)); } void processSerialCommands() { if (Serial.available()) { String command = Serial.readStringUntil('\n'); command.trim(); if (command == "cal") { calibrateIMU(); } else if (command == "status") { printSystemStatus(); } else if (command == "reset") { ESP.restart(); } else if (command == "stats") { printStatistics(); } } } void printSystemStatus() { Serial.println("\n=== ESTADO SISTEMA IMU ==="); Serial.printf("Tiempo activo: %lu ms\n", millis()); Serial.printf("Calibrado: %s\n", calibration.is_calibrated ? "SI" : "NO"); Serial.printf("Frecuencia I2C: %lu Hz\n", I2C_FREQ); Serial.printf("Buffer datos: %d/%d\n", buffer_index, DATA_BUFFER_SIZE); Serial.printf("Clientes WiFi: %d\n", WiFi.softAPgetStationNum()); Serial.printf("Memoria libre: %d bytes\n", ESP.getFreeHeap()); Serial.println("========================"); } void printStatistics() { Serial.println("\n=== ESTADÍSTICAS ==="); Serial.printf("Muestras: %u\n", stats.sample_count); const char* labels[] = {"Ax", "Ay", "Az", "Gx", "Gy", "Gz"}; for (int i = 0; i < 6; i++) { Serial.printf("%s: Min=%.3f, Max=%.3f, RMS=%.3f\n", labels[i], stats.min_vals[i], stats.max_vals[i], stats.rms_vals[i]); } Serial.println("==================="); } uint32_t calculateChecksum() { uint32_t sum = 0; uint8_t* data = (uint8_t*)&calibration; for (int i = 0; i < sizeof(CalibrationData) - sizeof(uint32_t); i++) { sum += data[i]; } return sum; } void saveCalibration() { EEPROM.put(0, calibration); EEPROM.commit(); Serial.println("Calibración guardada en EEPROM"); } void loadCalibration() { EEPROM.get(0, calibration); if (calibration.checksum == calculateChecksum()) { Serial.println("Calibración cargada desde EEPROM"); } else { memset(&calibration, 0, sizeof(CalibrationData)); Serial.println("No se encontró calibración válida"); } } }; // Instancia global del procesador IMU IMUProcessor imu_processor; void setup() { if (!imu_processor.initialize()) { Serial.println("Error crítico en inicialización"); while (1) { delay(1000); } } Serial.println("\n=== SISTEMA IMU INICIADO ==="); Serial.println("Comandos disponibles:"); Serial.println("- 'cal': Calibrar IMU"); Serial.println("- 'status': Estado del sistema"); Serial.println("- 'stats': Estadísticas"); Serial.println("- 'reset': Reiniciar ESP32"); Serial.println("===========================\n"); } void loop() { imu_processor.processLoop(); }

Ejercicios Prácticos Avanzados

1

Configuración Básica I2C

Básico 20 min

Implementa un escáner I2C que detecte automáticamente todos los dispositivos conectados al bus y muestre sus direcciones en formato hexadecimal.

Objetivos:
  • Configurar el bus I2C con pines personalizados
  • Implementar detección automática de dispositivos
  • Mostrar resultados en monitor serie y OLED
  • Validar integridad de comunicación
2

Procesamiento Digital de Señales

Intermedio 35 min

Desarrolla un sistema de filtrado digital para las señales del MPU6050, implementando filtros paso bajo, paso alto y complementario.

Características:
  • Filtro Kalman para fusión de sensores
  • Detección de movimiento por umbral
  • Análisis espectral básico (FFT)
  • Compensación de deriva
3

Sistema de Alarmas Inteligente

Avanzado 45 min

Crea un sistema de seguridad que detecte patrones de movimiento anómalos y genere alertas automáticas.

Funcionalidades:
  • Aprendizaje de patrones normales
  • Detección de anomalías por machine learning básico
  • Sistema de notificaciones IoT
  • Registro de eventos con timestamp
4

Interface Gráfica Avanzada

Profesional 60 min

Desarrolla una interfaz gráfica completa para visualización 3D de la orientación del IMU con animaciones en tiempo real.

Componentes:
  • Representación 3D de orientación
  • Gráficas de tendencias históricas
  • Dashboard web responsive
  • API REST completa

Proyecto Industrial: Sistema de Monitoreo de Vibración

Sistema de Monitoreo Industrial de Maquinaria

Desarrollaremos un sistema completo de monitoreo de vibración para maquinaria industrial que utilice el MPU6050 para detectar patrones anómalos de vibración, predecir fallas y optimizar mantenimiento predictivo.

Componentes Requeridos
  • ESP32 DevKit v1
  • MPU6050 (sensor IMU 6-DOF)
  • Pantalla OLED SSD1306 (128x64)
  • Módulo microSD para logging
  • LEDs de estado (Verde, Amarillo, Rojo)
  • Buzzer para alarmas
  • Fuente de alimentación industrial 24V/5V
  • Caja protectora IP65
Especificaciones Técnicas
  • Frecuencia de muestreo: 1 kHz
  • Rango acelerómetro: ±16g
  • Rango giroscopio: ±2000°/s
  • Resolución de análisis: 0.1 Hz
  • Almacenamiento: 30 días de datos
  • Conectividad: WiFi + Ethernet
  • Protocolo industrial: Modbus TCP
  • Alimentación: 12-24V DC
Sistema Industrial de Monitoreo - Código Principal
// SISTEMA DE MONITOREO INDUSTRIAL DE VIBRACION
// ESP32 + MPU6050 + Sistema de Análisis Avanzado
// Versión: 3.0 Industrial

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// Configuración del sistema industrial
#define SYSTEM_VERSION "3.0"
#define SAMPLE_RATE 1000        // Hz - Frecuencia de muestreo industrial
#define FFT_SIZE 1024           // Tamaño FFT para análisis espectral
#define ALARM_THRESHOLD_HIGH 8.0 // g - Umbral alarma alta
#define ALARM_THRESHOLD_MED 4.0  // g - Umbral alarma media
#define DATA_RETENTION_DAYS 30   // Días de retención de datos

// Pines del sistema
#define MPU6050_ADDR 0x68
#define OLED_ADDR 0x3C
#define LED_NORMAL 2
#define LED_WARNING 4
#define LED_ALARM 5
#define BUZZER_PIN 18
#define SD_CS_PIN 15
#define RELAY_OUTPUT 19

// Configuración de red industrial
const char* ssid = "PLANTA_INDUSTRIAL";
const char* password = "Industria4.0";
const char* ntp_server = "pool.ntp.org";

class IndustrialVibrationMonitor {
private:
    // Componentes del sistema
    Adafruit_SSD1306 display;
    WebServer server;
    WiFiUDP ntpUDP;
    NTPClient timeClient;
    ModbusMaster modbus;
    
    // Estructuras de datos industriales
    struct VibrationData {
        float rms_x, rms_y, rms_z;     // RMS de vibración por eje
        float peak_x, peak_y, peak_z;   // Picos máximos
        float frequency_peak;            // Frecuencia dominante
        float temperature;               // Temperatura del sensor
        uint32_t timestamp;              // Timestamp Unix
        uint8_t alarm_status;            // 0=Normal, 1=Advertencia, 2=Alarma
    };
    
    struct MachineProfile {
        char name[32];
        float normal_rms[3];       // Niveles normales RMS por eje
        float warning_factor;      // Factor para advertencia (ej. 1.5x normal)
        float alarm_factor;        // Factor para alarma (ej. 3x normal)
        uint16_t rpm_nominal;      // RPM nominales de la máquina
        bool active;
    };
    
    struct SystemHealth {
        uint32_t uptime_seconds;
        uint16_t samples_per_second;
        uint8_t wifi_quality;
        float cpu_temperature;
        uint32_t free_memory;
        uint16_t sd_write_errors;
        bool ntp_synchronized;
    };
    
    // Variables del sistema
    VibrationData current_data;
    MachineProfile machine_config;
    SystemHealth health_status;
    
    // Buffers para procesamiento
    float accel_buffer_x[FFT_SIZE];
    float accel_buffer_y[FFT_SIZE];
    float accel_buffer_z[FFT_SIZE];
    uint16_t buffer_index = 0;
    
    // Sistema de alarmas
    bool alarm_active = false;
    bool warning_active = false;
    uint32_t last_alarm_time = 0;
    uint16_t alarm_count_day = 0;
    
    // Análisis espectral
    float fft_result[FFT_SIZE/2];
    float frequency_bins[FFT_SIZE/2];
    
public:
    IndustrialVibrationMonitor() : 
        display(128, 64, &Wire, -1),
        server(80),
        timeClient(ntpUDP, ntp_server, 0, 60000) {
    }
    
    bool initializeSystem() {
        Serial.begin(115200);
        Serial.println("=== SISTEMA INDUSTRIAL DE MONITOREO ===");
        Serial.printf("Versión: %s\n", SYSTEM_VERSION);
        
        // Inicializar componentes críticos
        if (!initializeHardware()) return false;
        if (!initializeNetwork()) return false;
        if (!initializeStorage()) return false;
        
        // Cargar configuración de máquina
        loadMachineProfile();
        
        // Configurar sistema de tiempo
        timeClient.begin();
        updateSystemTime();
        
        // Inicializar Modbus (protocolo industrial)
        modbus.begin(1, Serial2);
        
        // Configurar alarmas y salidas
        setupAlarmSystem();
        
        Serial.println("Sistema inicializado correctamente");
        return true;
    }
    
private:
    bool initializeHardware() {
        // Configurar pines de salida
        pinMode(LED_NORMAL, OUTPUT);
        pinMode(LED_WARNING, OUTPUT);
        pinMode(LED_ALARM, OUTPUT);
        pinMode(BUZZER_PIN, OUTPUT);
        pinMode(RELAY_OUTPUT, OUTPUT);
        
        // Inicializar I2C
        Wire.begin(21, 22);
        Wire.setClock(400000);
        
        // Verificar MPU6050
        Wire.beginTransmission(MPU6050_ADDR);
        if (Wire.endTransmission() != 0) {
            Serial.println("ERROR: MPU6050 no detectado");
            return false;
        }
        
        // Configurar MPU6050 para aplicación industrial
        configureMPU6050Industrial();
        
        // Inicializar OLED
        if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
            Serial.println("ERROR: OLED no inicializado");
            return false;
        }
        
        display.clearDisplay();
        display.setTextColor(SSD1306_WHITE);
        display.setTextSize(1);
        display.setCursor(0, 0);
        display.println("SISTEMA INDUSTRIAL");
        display.println("Inicializando...");
        display.display();
        
        return true;
    }
    
    void configureMPU6050Industrial() {
        // Configuración optimizada para monitoreo industrial
        writeRegister(0x6B, 0x00);  // Despertar MPU6050
        delay(100);
        
        // Configurar filtro paso bajo a 94 Hz (ideal para análisis de vibración)
        writeRegister(0x1A, 0x02);
        
        // Configurar acelerómetro a ±16g (máximo rango para aplicaciones industriales)
        writeRegister(0x1C, 0x18);
        
        // Configurar giroscopio a ±2000°/s
        writeRegister(0x1B, 0x18);
        
        // Configurar frecuencia de muestreo (1 kHz)
        writeRegister(0x19, 0x07);  // Sample Rate = Gyro Rate / (1 + 7) = 1kHz
        
        Serial.println("MPU6050 configurado para monitoreo industrial");
    }
    
    void writeRegister(uint8_t reg, uint8_t value) {
        Wire.beginTransmission(MPU6050_ADDR);
        Wire.write(reg);
        Wire.write(value);
        Wire.endTransmission(true);
    }
    
    bool initializeNetwork() {
        WiFi.begin(ssid, password);
        
        int attempts = 0;
        while (WiFi.status() != WL_CONNECTED && attempts < 20) {
            delay(500);
            Serial.print(".");
            attempts++;
        }
        
        if (WiFi.status() != WL_CONNECTED) {
            Serial.println("ERROR: No se pudo conectar a WiFi");
            return false;
        }
        
        Serial.println();
        Serial.printf("WiFi conectado. IP: %s\n", WiFi.localIP().toString().c_str());
        
        // Configurar servidor web industrial
        setupWebServer();
        
        return true;
    }
    
    bool initializeStorage() {
        if (!SPIFFS.begin(true)) {
            Serial.println("ERROR: SPIFFS no inicializado");
            return false;
        }
        
        if (!SD.begin(SD_CS_PIN)) {
            Serial.println("WARNING: SD no disponible - solo almacenamiento interno");
        }
        
        return true;
    }
    
    void setupAlarmSystem() {
        // Test inicial de alarmas
        digitalWrite(LED_NORMAL, HIGH);
        delay(200);
        digitalWrite(LED_WARNING, HIGH);
        delay(200);
        digitalWrite(LED_ALARM, HIGH);
        digitalWrite(BUZZER_PIN, HIGH);
        delay(200);
        
        // Apagar todas las alarmas
        digitalWrite(LED_NORMAL, LOW);
        digitalWrite(LED_WARNING, LOW);
        digitalWrite(LED_ALARM, LOW);
        digitalWrite(BUZZER_PIN, LOW);
        digitalWrite(RELAY_OUTPUT, LOW);
        
        Serial.println("Sistema de alarmas configurado");
    }
    
    void setupWebServer() {
        // Página principal de monitoreo
        server.on("/", HTTP_GET, [this]() {
            String html = generateIndustrialDashboard();
            server.send(200, "text/html", html);
        });
        
        // API de datos en tiempo real
        server.on("/api/data", HTTP_GET, [this]() {
            DynamicJsonDocument doc(2048);
            
            doc["vibration"]["rms_x"] = current_data.rms_x;
            doc["vibration"]["rms_y"] = current_data.rms_y;
            doc["vibration"]["rms_z"] = current_data.rms_z;
            doc["vibration"]["peak_x"] = current_data.peak_x;
            doc["vibration"]["peak_y"] = current_data.peak_y;
            doc["vibration"]["peak_z"] = current_data.peak_z;
            doc["vibration"]["frequency_peak"] = current_data.frequency_peak;
            doc["vibration"]["temperature"] = current_data.temperature;
            doc["vibration"]["alarm_status"] = current_data.alarm_status;
            
            doc["machine"]["name"] = machine_config.name;
            doc["machine"]["rpm_nominal"] = machine_config.rpm_nominal;
            doc["machine"]["active"] = machine_config.active;
            
            doc["system"]["uptime"] = health_status.uptime_seconds;
            doc["system"]["samples_per_second"] = health_status.samples_per_second;
            doc["system"]["wifi_quality"] = health_status.wifi_quality;
            doc["system"]["free_memory"] = health_status.free_memory;
            doc["system"]["timestamp"] = current_data.timestamp;
            
            String response;
            serializeJson(doc, response);
            server.send(200, "application/json", response);
        });
        
        // API de configuración
        server.on("/api/config", HTTP_POST, [this]() {
            if (server.hasArg("plain")) {
                DynamicJsonDocument doc(1024);
                deserializeJson(doc, server.arg("plain"));
                
                if (doc.containsKey("machine_name")) {
                    strncpy(machine_config.name, doc["machine_name"], 31);
                }
                if (doc.containsKey("rpm_nominal")) {
                    machine_config.rpm_nominal = doc["rpm_nominal"];
                }
                if (doc.containsKey("warning_factor")) {
                    machine_config.warning_factor = doc["warning_factor"];
                }
                if (doc.containsKey("alarm_factor")) {
                    machine_config.alarm_factor = doc["alarm_factor"];
                }
                
                saveMachineProfile();
                server.send(200, "text/plain", "Configuración actualizada");
            } else {
                server.send(400, "text/plain", "Datos inválidos");
            }
        });
        
        // Análisis de espectro de frecuencias
        server.on("/api/spectrum", HTTP_GET, [this]() {
            DynamicJsonDocument doc(4096);
            JsonArray frequencies = doc.createNestedArray("frequencies");
            JsonArray amplitudes = doc.createNestedArray("amplitudes");
            
            for (int i = 0; i < FFT_SIZE/2; i += 4) {  // Reducir datos para transmisión
                frequencies.add(frequency_bins[i]);
                amplitudes.add(fft_result[i]);
            }
            
            doc["peak_frequency"] = current_data.frequency_peak;
            doc["sample_rate"] = SAMPLE_RATE;
            
            String response;
            serializeJson(doc, response);
            server.send(200, "application/json", response);
        });
        
        server.begin();
        Serial.println("Servidor web industrial iniciado");
    }
    
public:
    void processIndustrialLoop() {
        static unsigned long last_sample = 0;
        static unsigned long last_display = 0;
        static unsigned long last_health = 0;
        static unsigned long last_log = 0;
        
        unsigned long now = millis();
        
        // Adquisición de datos a 1 kHz
        if (now - last_sample >= 1) {
            acquireVibrationSample();
            last_sample = now;
        }
        
        // Actualizar display cada 250ms
        if (now - last_display >= 250) {
            updateIndustrialDisplay();
            last_display = now;
        }
        
        // Actualizar estadísticas del sistema cada 5 segundos
        if (now - last_health >= 5000) {
            updateSystemHealth();
            last_health = now;
        }
        
        // Guardar datos cada minuto
        if (now - last_log >= 60000) {
            logVibrationData();
            last_log = now;
        }
        
        // Procesar servidor web
        server.handleClient();
        
        // Procesar comunicación Modbus
        processModbusComm();
        
        // Procesar sistema de alarmas
        processAlarmSystem();
    }
    
private:
    void acquireVibrationSample() {
        // Leer datos del MPU6050
        Wire.beginTransmission(MPU6050_ADDR);
        Wire.write(0x3B);  // Registro de acelerómetro
        Wire.endTransmission(false);
        Wire.requestFrom(MPU6050_ADDR, 14, true);
        
        // Leer acelerómetro (16-bit, ±16g)
        int16_t ax = (Wire.read() << 8) | Wire.read();
        int16_t ay = (Wire.read() << 8) | Wire.read();
        int16_t az = (Wire.read() << 8) | Wire.read();
        
        // Saltar temperatura por ahora
        Wire.read(); Wire.read();
        
        // Leer giroscopio
        int16_t gx = (Wire.read() << 8) | Wire.read();
        int16_t gy = (Wire.read() << 8) | Wire.read();
        int16_t gz = (Wire.read() << 8) | Wire.read();
        
        // Convertir a unidades físicas (g)
        float accel_x = ax / 2048.0;  // ±16g range
        float accel_y = ay / 2048.0;
        float accel_z = az / 2048.0;
        
        // Almacenar en buffer para análisis FFT
        accel_buffer_x[buffer_index] = accel_x;
        accel_buffer_y[buffer_index] = accel_y;
        accel_buffer_z[buffer_index] = accel_z;
        
        buffer_index = (buffer_index + 1) % FFT_SIZE;
        
        // Calcular métricas en tiempo real
        if (buffer_index == 0) {  // Buffer completo, procesar
            processVibrationAnalysis();
        }
    }
    
    void processVibrationAnalysis() {
        // Calcular RMS para cada eje
        current_data.rms_x = calculateRMS(accel_buffer_x, FFT_SIZE);
        current_data.rms_y = calculateRMS(accel_buffer_y, FFT_SIZE);
        current_data.rms_z = calculateRMS(accel_buffer_z, FFT_SIZE);
        
        // Encontrar picos máximos
        current_data.peak_x = findPeak(accel_buffer_x, FFT_SIZE);
        current_data.peak_y = findPeak(accel_buffer_y, FFT_SIZE);
        current_data.peak_z = findPeak(accel_buffer_z, FFT_SIZE);
        
        // Análisis de frecuencias (FFT simplificado)
        performFFTAnalysis();
        
        // Actualizar timestamp
        current_data.timestamp = now();
        
        // Evaluar estado de alarma
        evaluateAlarmConditions();
    }
    
    float calculateRMS(float* data, int size) {
        float sum_squares = 0;
        for (int i = 0; i < size; i++) {
            sum_squares += data[i] * data[i];
        }
        return sqrt(sum_squares / size);
    }
    
    float findPeak(float* data, int size) {
        float max_val = 0;
        for (int i = 0; i < size; i++) {
            float abs_val = abs(data[i]);
            if (abs_val > max_val) {
                max_val = abs_val;
            }
        }
        return max_val;
    }
    
    void performFFTAnalysis() {
        // FFT simplificada para encontrar frecuencia dominante
        // En aplicación real, usar librería FFT optimizada
        
        float max_magnitude = 0;
        int peak_bin = 0;
        
        // Análisis de frecuencias básico
        for (int freq_bin = 1; freq_bin < FFT_SIZE/2; freq_bin++) {
            float magnitude = 0;
            
            // Calcular magnitud para esta frecuencia
            for (int i = 0; i < FFT_SIZE; i++) {
                float angle = 2.0 * PI * freq_bin * i / FFT_SIZE;
                magnitude += accel_buffer_x[i] * cos(angle);
            }
            
            magnitude = abs(magnitude);
            fft_result[freq_bin] = magnitude;
            frequency_bins[freq_bin] = (float)freq_bin * SAMPLE_RATE / FFT_SIZE;
            
            if (magnitude > max_magnitude) {
                max_magnitude = magnitude;
                peak_bin = freq_bin;
            }
        }
        
        current_data.frequency_peak = frequency_bins[peak_bin];
    }
    
    void evaluateAlarmConditions() {
        float max_rms = max({current_data.rms_x, current_data.rms_y, current_data.rms_z});
        
        // Evaluar según configuración de máquina
        if (max_rms > machine_config.normal_rms[0] * machine_config.alarm_factor) {
            current_data.alarm_status = 2;  // Alarma alta
        } else if (max_rms > machine_config.normal_rms[0] * machine_config.warning_factor) {
            current_data.alarm_status = 1;  // Advertencia
        } else {
            current_data.alarm_status = 0;  // Normal
        }
    }
    
    void updateIndustrialDisplay() {
        display.clearDisplay();
        
        // Header con estado del sistema
        display.setTextSize(1);
        display.setCursor(0, 0);
        display.printf("MONITOREO INDUSTRIAL");
        
        // Línea de estado
        display.setCursor(0, 10);
        const char* status_text[] = {"NORMAL", "ALERTA", "ALARMA"};
        display.printf("Estado: %s", status_text[current_data.alarm_status]);
        
        // Valores RMS
        display.setCursor(0, 20);
        display.printf("RMS X: %.2f g", current_data.rms_x);
        display.setCursor(0, 30);
        display.printf("RMS Y: %.2f g", current_data.rms_y);
        display.setCursor(0, 40);
        display.printf("RMS Z: %.2f g", current_data.rms_z);
        
        // Frecuencia dominante y temperatura
        display.setCursor(0, 50);
        display.printf("Freq: %.1f Hz", current_data.frequency_peak);
        display.setCursor(0, 58);
        display.printf("Temp: %.1f C", current_data.temperature);
        
        display.display();
    }
    
    void processAlarmSystem() {
        // Gestión de LEDs de estado
        digitalWrite(LED_NORMAL, current_data.alarm_status == 0);
        digitalWrite(LED_WARNING, current_data.alarm_status == 1);
        digitalWrite(LED_ALARM, current_data.alarm_status == 2);
        
        // Gestión de buzzer y relay para alarmas críticas
        if (current_data.alarm_status == 2) {
            if (!alarm_active) {
                alarm_active = true;
                last_alarm_time = millis();
                alarm_count_day++;
                
                // Activar alarma sonora por 5 segundos
                digitalWrite(BUZZER_PIN, HIGH);
                digitalWrite(RELAY_OUTPUT, HIGH);  // Relay para parada de emergencia
                
                Serial.printf("ALARMA ACTIVADA - RMS Max: %.2f g\n", 
                             max({current_data.rms_x, current_data.rms_y, current_data.rms_z}));
            }
            
            // Desactivar alarma sonora después de 5 segundos
            if (millis() - last_alarm_time > 5000) {
                digitalWrite(BUZZER_PIN, LOW);
            }
        } else {
            if (alarm_active) {
                alarm_active = false;
                digitalWrite(BUZZER_PIN, LOW);
                digitalWrite(RELAY_OUTPUT, LOW);
                Serial.println("Alarma desactivada");
            }
        }
    }
    
    void updateSystemHealth() {
        health_status.uptime_seconds = millis() / 1000;
        health_status.samples_per_second = 1000;  // Fijo a 1kHz
        health_status.wifi_quality = map(WiFi.RSSI(), -100, -30, 0, 100);
        health_status.cpu_temperature = temperatureRead();
        health_status.free_memory = ESP.getFreeHeap();
        health_status.ntp_synchronized = timeClient.update();
        
        // Actualizar tiempo del sistema
        if (health_status.ntp_synchronized) {
            setTime(timeClient.getEpochTime());
        }
    }
    
    void logVibrationData() {
        // Crear registro JSON
        DynamicJsonDocument doc(1024);
        doc["timestamp"] = current_data.timestamp;
        doc["rms_x"] = current_data.rms_x;
        doc["rms_y"] = current_data.rms_y;
        doc["rms_z"] = current_data.rms_z;
        doc["peak_x"] = current_data.peak_x;
        doc["peak_y"] = current_data.peak_y;
        doc["peak_z"] = current_data.peak_z;
        doc["frequency_peak"] = current_data.frequency_peak;
        doc["alarm_status"] = current_data.alarm_status;
        doc["temperature"] = current_data.temperature;
        
        String log_entry;
        serializeJson(doc, log_entry);
        
        // Guardar en SD si está disponible
        if (SD.begin(SD_CS_PIN)) {
            String filename = "/vibration_" + String(year()) + 
                             String(month()) + String(day()) + ".json";
            File log_file = SD.open(filename, FILE_APPEND);
            if (log_file) {
                log_file.println(log_entry);
                log_file.close();
            }
        }
        
        Serial.printf("Log guardado - RMS: %.2f,%.2f,%.2f g\n", 
                     current_data.rms_x, current_data.rms_y, current_data.rms_z);
    }
    
    void processModbusComm() {
        // Comunicación Modbus para integración con sistemas SCADA
        // Registros Modbus para datos de vibración
        static uint16_t modbus_registers[20];
        
        // Convertir datos a registros Modbus (16-bit)
        modbus_registers[0] = (uint16_t)(current_data.rms_x * 1000);  // RMS X en milli-g
        modbus_registers[1] = (uint16_t)(current_data.rms_y * 1000);  // RMS Y en milli-g
        modbus_registers[2] = (uint16_t)(current_data.rms_z * 1000);  // RMS Z en milli-g
        modbus_registers[3] = (uint16_t)(current_data.peak_x * 1000); // Peak X en milli-g
        modbus_registers[4] = (uint16_t)(current_data.peak_y * 1000); // Peak Y en milli-g
        modbus_registers[5] = (uint16_t)(current_data.peak_z * 1000); // Peak Z en milli-g
        modbus_registers[6] = (uint16_t)(current_data.frequency_peak * 10); // Freq en deci-Hz
        modbus_registers[7] = (uint16_t)(current_data.temperature * 100);   // Temp en centi-°C
        modbus_registers[8] = current_data.alarm_status;
        modbus_registers[9] = health_status.samples_per_second;
        modbus_registers[10] = (uint16_t)(health_status.uptime_seconds & 0xFFFF);
        modbus_registers[11] = (uint16_t)(health_status.uptime_seconds >> 16);
        
        // En aplicación real, responder a consultas Modbus aquí
    }
    
    String generateIndustrialDashboard() {
        return R"(


    Monitor Industrial de Vibración
    
    


    

🏭 SISTEMA DE MONITOREO INDUSTRIAL

Análisis de Vibración en Tiempo Real - ESP32 + MPU6050

📊 Vibración RMS

Eje X: 0.00 g
Eje Y: 0.00 g
Eje Z: 0.00 g

⚡ Picos Máximos

Peak X: 0.00 g
Peak Y: 0.00 g
Peak Z: 0.00 g

🌡️ Estado del Sistema

Estado: Normal
Frecuencia: 0 Hz
Temperatura: 0°C
Normal Alerta Alarma

⚙️ Información de Máquina

Nombre: Cargando...
RPM Nominal: 0
Tiempo Activo: 0h 0m

📈 Espectro de Frecuencias

)"; } void loadMachineProfile() { // Configuración por defecto strcpy(machine_config.name, "Maquina Industrial"); machine_config.normal_rms[0] = 1.0; // 1g RMS normal machine_config.normal_rms[1] = 1.0; machine_config.normal_rms[2] = 1.0; machine_config.warning_factor = 1.5; // 1.5x normal = advertencia machine_config.alarm_factor = 3.0; // 3x normal = alarma machine_config.rpm_nominal = 1800; // RPM típicos machine_config.active = true; // Cargar desde SPIFFS si existe if (SPIFFS.exists("/machine_config.json")) { File config_file = SPIFFS.open("/machine_config.json", "r"); if (config_file) { DynamicJsonDocument doc(1024); deserializeJson(doc, config_file); if (doc.containsKey("name")) { strncpy(machine_config.name, doc["name"], 31); } if (doc.containsKey("normal_rms")) { machine_config.normal_rms[0] = doc["normal_rms"]; machine_config.normal_rms[1] = doc["normal_rms"]; machine_config.normal_rms[2] = doc["normal_rms"]; } if (doc.containsKey("warning_factor")) { machine_config.warning_factor = doc["warning_factor"]; } if (doc.containsKey("alarm_factor")) { machine_config.alarm_factor = doc["alarm_factor"]; } if (doc.containsKey("rpm_nominal")) { machine_config.rpm_nominal = doc["rpm_nominal"]; } config_file.close(); Serial.println("Configuración de máquina cargada"); } } } void saveMachineProfile() { DynamicJsonDocument doc(1024); doc["name"] = machine_config.name; doc["normal_rms"] = machine_config.normal_rms[0]; doc["warning_factor"] = machine_config.warning_factor; doc["alarm_factor"] = machine_config.alarm_factor; doc["rpm_nominal"] = machine_config.rpm_nominal; doc["active"] = machine_config.active; File config_file = SPIFFS.open("/machine_config.json", "w"); if (config_file) { serializeJson(doc, config_file); config_file.close(); Serial.println("Configuración de máquina guardada"); } } void updateSystemTime() { if (WiFi.status() == WL_CONNECTED) { timeClient.update(); setTime(timeClient.getEpochTime()); Serial.printf("Hora sincronizada: %02d:%02d:%02d\n", hour(), minute(), second()); } } }; // Instancia global del sistema IndustrialVibrationMonitor vibration_monitor; void setup() { // Inicializar sistema industrial if (!vibration_monitor.initializeSystem()) { Serial.println("ERROR CRÍTICO: Sistema no inicializado"); Serial.println("Verificar conexiones de hardware"); // Parpadear LED de error pinMode(LED_BUILTIN, OUTPUT); while (1) { digitalWrite(LED_BUILTIN, HIGH); delay(200); digitalWrite(LED_BUILTIN, LOW); delay(200); } } Serial.println("=== SISTEMA INDUSTRIAL OPERATIVO ==="); Serial.println("Monitor de vibración iniciado correctamente"); Serial.printf("Acceso web: http://%s\n", WiFi.localIP().toString().c_str()); Serial.println("Frecuencia de muestreo: 1 kHz"); Serial.println("Análisis espectral activo"); Serial.println("Sistema de alarmas operativo"); Serial.println("======================================"); } void loop() { // Ejecutar loop principal del sistema industrial vibration_monitor.processIndustrialLoop(); }
Características Avanzadas del Sistema Industrial
Análisis Espectral
  • FFT en tiempo real para análisis de frecuencias
  • Detección de armónicos y resonancias
  • Identificación de patrones de falla
  • Trending de frecuencias dominantes
Sistema de Seguridad
  • Múltiples niveles de alarma configurables
  • Parada de emergencia automática
  • Registro de eventos con timestamp
  • Notificaciones por múltiples canales
Gestión de Datos
  • Almacenamiento local y remoto
  • Compresión automática de datos históricos
  • Exportación en formatos estándar
  • Sincronización con sistemas SCADA
Conectividad Industrial
  • Protocolo Modbus TCP/RTU
  • OPC UA para industria 4.0
  • MQTT para IoT industrial
  • API REST para integración personalizada

Solución de Problemas Comunes

En el desarrollo de sistemas I2C con ESP32, MPU6050 y pantallas OLED, es común encontrar diversos problemas técnicos. Esta sección aborda las situaciones más frecuentes con sus respectivas soluciones profesionales.

Problemas Comunes
MPU6050 no se detecta
  • Error en inicialización I2C
  • Dirección I2C incorrecta (0x68 vs 0x69)
  • Conexiones físicas defectuosas
  • Voltaje de alimentación insuficiente
OLED no muestra información
  • Pantalla no inicializada correctamente
  • Dirección I2C errónea (0x3C vs 0x3D)
  • Buffer no actualizado con display.display()
  • Conflicto en el bus I2C
Lecturas erráticas del sensor
  • Interferencias electromagnéticas
  • Calibración insuficiente
  • Frecuencia de muestreo inadecuada
  • Filtrado de datos deficiente
Problemas de comunicación I2C
  • Resistencias pull-up ausentes o incorrectas
  • Velocidad del bus demasiado alta
  • Cables demasiado largos
  • Múltiples dispositivos con misma dirección
Soluciones Técnicas
Verificación de dispositivos I2C
void scanI2CDevices() {
  Serial.println("Escaneando I2C...");
  for (byte i = 8; i < 120; i++) {
    Wire.beginTransmission(i);
    if (Wire.endTransmission() == 0) {
      Serial.printf("Dispositivo: 0x%02X\n", i);
    }
  }
}
Configuración robusta I2C
// Configuración con manejo de errores
Wire.begin(21, 22);  // SDA, SCL
Wire.setClock(100000); // 100kHz estable
Wire.setTimeOut(5000); // Timeout 5ms

// Verificar conexión
Wire.beginTransmission(device_addr);
if (Wire.endTransmission() != 0) {
  Serial.println("Error: Dispositivo no responde");
}
Inicialización OLED robusta
bool initOLED() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("OLED falló");
    return false;
  }
  display.clearDisplay();
  display.display(); // Actualizar buffer
  return true;
}
Sistema de diagnóstico
void diagnosticSystem() {
  Serial.println("=== DIAGNÓSTICO SISTEMA ===");
  Serial.printf("I2C Clock: %d Hz\n", Wire.getClock());
  Serial.printf("Dispositivos detectados: %d\n", 
                countI2CDevices());
  Serial.printf("MPU6050 ID: 0x%02X\n", 
                readMPU6050Register(0x75));
}

Referencias Técnicas y Documentación

Documentación Oficial
Estándares Industriales
  • ISO 10816: Vibración mecánica - Evaluación de vibración de máquinas
  • IEEE 1451: Estándar para sensores inteligentes
  • IEC 61508: Seguridad funcional en sistemas E/E/PE
Recursos Adicionales
  • Filtros Digitales: Implementación de filtros Kalman y complementarios
  • Análisis FFT: Algoritmos de transformada rápida de Fourier
  • Protocolos Industriales: Modbus, OPC UA, MQTT Industrial
  • Mantenimiento Predictivo: Técnicas de análisis de vibración
Herramientas de Desarrollo
  • PlatformIO: Entorno de desarrollo profesional
  • ESP32 Arduino Core: Framework de desarrollo
  • FreeRTOS: Sistema operativo en tiempo real
  • MATLAB/Simulink: Modelado y simulación