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.
#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
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
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
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
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 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
- ESP32 Technical Reference: Manual Técnico ESP32
- MPU6050 Datasheet: Especificaciones MPU6050
- SSD1306 OLED Driver: Driver SSD1306
- I2C Specification: Protocolo I2C v2.1
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