Caso práctico: Vibración predictiva LoRa con ESP32-S3

Caso práctico: Vibración predictiva LoRa con ESP32-S3 — hero

Objetivo y caso de uso

Qué construirás: Un sistema de detección de vibraciones predictivo utilizando LoRa y ESP32-S3 para monitorizar maquinaria industrial.

Para qué sirve

  • Monitoreo continuo de vibraciones en motores eléctricos para prevenir fallos.
  • Detección temprana de desbalance en ejes rotativos en fábricas.
  • Transmisión de datos de vibración en tiempo real a través de LoRa para análisis remoto.
  • Integración con sistemas de gestión de mantenimiento para optimizar intervenciones.

Resultado esperado

  • Reducción del 30% en tiempos de inactividad no planificada.
  • Latencia de transmisión de datos menor a 5 segundos.
  • Capacidad de enviar hasta 100 paquetes de datos por hora.
  • Precisión en la medición de vibraciones de ±0.1 g.

Público objetivo: Ingenieros de mantenimiento; Nivel: alto

Arquitectura/flujo: Sensores ICM-42688-P conectados al ESP32-S3, que envían datos a través de LoRa a un servidor para análisis.

Nivel: alto

Prerrequisitos

  • Sistema operativo (elige uno; todos probados con las versiones indicadas):
  • Windows 11 23H2 (Build 22631)
  • Ubuntu 22.04.4 LTS
  • macOS 14 Sonoma

  • Toolchain exacta:

  • Python 3.10.x (Ubuntu/macOS) o Python 3.11.x (Windows, vía Microsoft Store). Verifica con: python –version
  • PlatformIO Core 6.1.15 (CLI) y PlatformIO IDE 3.1.1 (VS Code 1.93+)
  • Verifica: pio –version → PlatformIO Core, Home y Python
  • Plataforma ESP32 para PlatformIO:
  • platform = espressif32 @ 6.6.0
  • framework-arduinoespressif32 @ 3.0.7 (Arduino-ESP32 basado en ESP-IDF 5.x)
  • toolchain-xtensa-esp32s3 @ 12.2.0+2023r1 (xtensa-esp32s3-elf-gcc 12.2.0)
  • Librerías Arduino (fijadas para reproducibilidad):
  • RadioLib @ 6.5.0 (manejo SX1276/RFM95W)
  • hideakitai/ICM42688 @ 0.2.2 (ICM-42688-P por I2C)
  • arduinoFFT @ 2.0.3 (FFT en punto flotante)
  • cryptoauthlib @ 3.3.3 (Microchip ATECC608A)
  • Controladores USB:
  • ESP32-S3-DevKitC-1 usa USB nativo CDC/JTAG; en Windows y macOS, no se requieren drivers adicionales.
  • Si usas un adaptador USB-UART alternativo: CP210x v10.1.12 (Windows) / v6.0.1 (macOS), o CH34x v1.7 (Windows/macOS).

Notas:
– La ESP32-S3-DevKitC-1 suele programarse por USB nativo (Type-C). Asegúrate de que el “USB CDC On Boot” está activo (lo fijaremos desde PlatformIO).
– En Linux, añade tu usuario a dialout para acceder a /dev/ttyACM*: sudo usermod -a -G dialout $USER y relogin.

Materiales

  • Conjunto exacto:
  • 1 × ESP32-S3-DevKitC-1 (ESP32-S3)
  • 1 × RFM95W (SX1276) 868/915 MHz (elige la variante para tu región, p. ej. 868 MHz para EU868)
  • 1 × ICM-42688-P (IMU 6 DoF; breakout típico: GND/VCC/SDA/SCL/AD0 exponiendo I2C)
  • 1 × ATECC608A (co-procesador criptográfico I2C; breakout típico con GND/VCC/SDA/SCL)
  • Cables dupont macho-macho
  • 1 × Cable USB-C de datos

  • Opcionales para validación adicional:

  • 1 × Segundo nodo LoRa (otro ESP32 + RFM95W) para actuar como receptor
  • Analizador lógico o SDR (para verificar radiofrecuencia)
  • Acelerómetro calibrado o fuente de vibración (motor desbalanceado, mesa vibratoria)

Preparación y conexión

Mapa de pines y buses

  • Buses elegidos (coherentes con ESP32-S3-DevKitC-1 y el objetivo):
  • I2C0 para IMU y ATECC608A a 400 kHz
  • SDA = GPIO 8
  • SCL = GPIO 9
  • SPI para LoRa RFM95W (SX1276)
  • SCK = GPIO 12
  • MISO = GPIO 13
  • MOSI = GPIO 11
  • NSS/CS = GPIO 10
  • RST = GPIO 18
  • DIO0 = GPIO 17 (IRQ principal)
  • DIO1 = GPIO 16 (IRQ adicional, opcional)

  • Alimentación:

  • 3V3 desde la DevKitC-1 para RFM95W, ICM-42688-P y ATECC608A
  • GND común para todos

  • Dirección I2C:

  • ICM-42688-P en 0x68 (AD0 a GND; si AD0 a VCC → 0x69)
  • ATECC608A en 0x60 (típico)

Tabla de conexiones

Módulo Señal Pin en módulo Pin en ESP32-S3-DevKitC-1
RFM95W (SX1276) VCC 3V3 3V3
RFM95W (SX1276) GND GND GND
RFM95W (SX1276) SCK SCK GPIO 12
RFM95W (SX1276) MISO MISO GPIO 13
RFM95W (SX1276) MOSI MOSI GPIO 11
RFM95W (SX1276) NSS (CS) NSS GPIO 10
RFM95W (SX1276) RST RST GPIO 18
RFM95W (SX1276) DIO0 DIO0 GPIO 17
RFM95W (SX1276) DIO1 (opcional) DIO1 GPIO 16
ICM-42688-P VCC 3V3 3V3
ICM-42688-P GND GND GND
ICM-42688-P SDA SDA GPIO 8
ICM-42688-P SCL SCL GPIO 9
ICM-42688-P AD0 AD0 a GND (0x68)
ATECC608A VCC 3V3 3V3
ATECC608A GND GND GND
ATECC608A SDA SDA GPIO 8
ATECC608A SCL SCL GPIO 9

Notas:
– Evitamos pines de “strap” del ESP32-S3 (GPIO0, GPIO3, GPIO45, GPIO46) para señales que puedan quedar forzadas al arranque.
– USB nativo del S3 usa GPIO19/20 internamente; no los empleamos.
– Mantén cables SPI cortos y con GND cercano para LoRa. Para I2C, 10–20 cm máximo a 400 kHz.

Código completo

A continuación encontrarás:
1) platformio.ini con versiones fijadas y banderas clave para USB CDC.
2) main.cpp con:
– Inicialización del I2C, IMU ICM-42688-P.
– Inicialización de ATECC608A y lectura de número de serie (para ID de dispositivo).
– Inicialización de LoRa (SX1276) con RadioLib.
– Muestreo a ~1.6 kS/s, cálculo de características (RMS, crest factor, kurtosis, centróide espectral) usando arduinoFFT.
– Detección de anomalías por z-score vs. baseline.
– Envío LoRa de un payload binario compacto con ID, contador, features y flag.

platformio.ini

; platformio.ini — lora-predictive-vibration-icm42688
[env:esp32-s3-devkitc-1]
platform = espressif32 @ 6.6.0
board = esp32-s3-devkitc-1
framework = arduino

; Paquetes y toolchain fijados para reproducibilidad
platform_packages =
  framework-arduinoespressif32 @ 3.0.7
  toolchain-xtensa-esp32s3 @ 12.2.0+2023r1

; Librerías específicas
lib_deps =
  jgromes/RadioLib @ 6.5.0
  hideakitai/ICM42688 @ 0.2.2
  arduinoFFT @ 2.0.3
  microchip/cryptoauthlib @ 3.3.3

; Velocidad de monitor
monitor_speed = 115200

; Habilitar USB CDC en boot y seleccionar Serial por USB nativo
build_flags =
  -DARDUINO_USB_MODE=1
  -DARDUINO_USB_CDC_ON_BOOT=1
  -DCORE_DEBUG_LEVEL=3

; Región LoRa (ajusta a tu país/región)
; EU868: 868.1 MHz; US915: 915.0 MHz
build_unflags = -Werror

; Subir por USB (CDC)
upload_protocol = esptool
; En Linux suele ser /dev/ttyACM0 o /dev/ttyACM1
; upload_port = /dev/ttyACM0
; monitor_port = /dev/ttyACM0

; Fijar frecuencia de CPU si necesitas determinismo extra (opcional)
; board_build.f_cpu = 240000000L

src/main.cpp

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <RadioLib.h>
#include <ICM42688.h>     // hideakitai/ICM42688
#include <arduinoFFT.h>   // v2.0.3
#include "cryptoauthlib.h"

// ---------------------- Pines ----------------------
#define LORA_SCK   12
#define LORA_MISO  13
#define LORA_MOSI  11
#define LORA_CS    10
#define LORA_RST   18
#define LORA_DIO0  17
#define LORA_DIO1  16

#define I2C_SDA     8
#define I2C_SCL     9

// ---------------------- Config LoRa ----------------
#ifndef LORA_FREQ_MHZ
  #define LORA_FREQ_MHZ 868.1  // Ajusta a tu región (EU868). US915 -> 915.0
#endif

// RadioLib: crear módulo SX1276 con SPI global y mapeo de pines
Module loraModule(LORA_CS, LORA_DIO0, LORA_RST, LORA_DIO1);
SX1276 radio(&loraModule);

// ---------------------- IMU ICM-42688-P -----------
ICM42688 imu(Wire);  // hideakitai: constructor por I2C
static const uint8_t ICM42688_ADDR = 0x68;  // AD0 a GND

// ---------------------- FFT y señal ----------------
constexpr size_t N_SAMPLES = 512;    // ventana (potencia de 2 para FFT)
constexpr float FS_HZ = 1600.0f;     // tasa de muestreo (IMU ODR >= 1.6kHz)
double vReal[N_SAMPLES];
double vImag[N_SAMPLES];
arduinoFFT FFT(vReal, vImag, N_SAMPLES, FS_HZ);

// ---------------------- Baseline/anomalía ----------
struct Features {
  float rms;
  float crest_factor;
  float kurtosis;
  float spectral_centroid;
};

// Baseline estadístico simple
Features baselineMean = {0};
Features baselineStd  = {1,1,1,1};
bool baselineReady = false;
uint16_t baselineWindows = 0;
const uint16_t WARMUP_WINDOWS = 20; // primeras ventanas para baseline

// ---------------------- ATECC608A ------------------
bool ateccReady = false;
uint8_t ateccSerial[9] = {0}; // 9 bytes serial

// ---------------------- Utilidad -------------------
uint32_t frameCounter = 0;

// Cuantización de features para payload
int16_t q15_from_float(float x, float scale) {
  float v = x * scale;
  if (v > 32767.0f) v = 32767.0f;
  if (v < -32768.0f) v = -32768.0f;
  return (int16_t)lroundf(v);
}

// ---------------------- ATECC: init y serial -------
bool initATECC() {
  ATCAIfaceCfg cfg = cfg_ateccx08a_i2c_default();
  // Ajustar I2C en ESP32-S3
  cfg.atcai2c.address = 0x60;
  cfg.atcai2c.bus = 0;      // Wire
  cfg.atcai2c.baud = 400000;

  ATCA_STATUS s = atcab_init(&cfg);
  if (s != ATCA_SUCCESS) {
    Serial.printf("ATECC init fallo: %d\n", s);
    return false;
  }
  s = atcab_read_serial_number(ateccSerial);
  if (s != ATCA_SUCCESS) {
    Serial.printf("ATECC read serial fallo: %d\n", s);
    return false;
  }
  Serial.print("ATECC608A S/N: ");
  for (int i = 0; i < 9; i++) {
    Serial.printf("%02X", ateccSerial[i]);
  }
  Serial.println();
  return true;
}

// ---------------------- IMU: init -------------------
bool initIMU() {
  Wire.begin(I2C_SDA, I2C_SCL, 400000); // 400 kHz
  delay(10);
  if (!imu.begin(ICM42688_ADDR)) {
    Serial.println("ICM-42688-P no responde en 0x68. Revisa cableado/AD0.");
    return false;
  }
  // Configurar ODR y rangos (consulta API de hideakitai/ICM42688)
  imu.setAccelODR(ICM42688::odr::odr1k6); // ~1.6 kHz
  imu.setGyroODR(ICM42688::odr::odr1k6);
  imu.setAccelRange(ICM42688::accel_range::g16); // mayor rango para vibración
  imu.setGyroRange(ICM42688::gyro_range::dps2000);
  imu.setAccelLPF(ICM42688::accel_lpf::lpf_180Hz);
  imu.setGyroLPF(ICM42688::gyro_lpf::lpf_180Hz);
  imu.enableAccel(true);
  imu.enableGyro(false); // enfocamos vibración en acelerómetro
  delay(50);
  Serial.println("ICM-42688-P inicializado.");
  return true;
}

// ---------------------- LoRa: init ------------------
bool initLoRa() {
  // Inicializar SPI con pines definidos
  SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);

  int8_t state = radio.begin(LORA_FREQ_MHZ, 125.0, 9, 5, 0x34, 14, 8, 0);
  // Parámetros: freq MHz, BW kHz, SF, CR denom, syncWord, power dBm, preamble len, gain
  // Ajustados a SF9 para sensibilidad/balance
  if (state != RADIOLIB_ERR_NONE) {
    Serial.printf("LoRa init fallo, code %d\n", state);
    return false;
  }
  // Opcionales: fijar tiempo de transmisión (TCXO no usado), cabecera explícita, CRC
  radio.setCRC(true);
  Serial.println("SX1276 (RFM95W) inicializado.");
  return true;
}

// ------------------- Muestreo y features ------------
// Calcula features sobre vReal (una ventana) y rellena spectral centroid
Features computeFeatures() {
  // 1) RMS, crest factor, kurtosis (dominio tiempo)
  double mean = 0, sum2 = 0, maxAbs = 0, sum4 = 0;
  for (size_t i = 0; i < N_SAMPLES; i++) {
    double x = vReal[i];
    mean += x;
    sum2 += x * x;
    double ax = fabs(x);
    if (ax > maxAbs) maxAbs = ax;
  }
  mean /= N_SAMPLES;
  double var = 0;
  for (size_t i = 0; i < N_SAMPLES; i++) {
    double d = vReal[i] - mean;
    var += d * d;
    sum4 += d * d * d * d;
  }
  var /= N_SAMPLES;
  double rms = sqrt(sum2 / N_SAMPLES);
  double sigma = sqrt(var + 1e-12);
  double kurt = (sum4 / N_SAMPLES) / (pow(sigma, 4) + 1e-12);

  // crest factor = max(|x|)/RMS
  double crest = (rms > 1e-9) ? (maxAbs / rms) : 0.0;

  // 2) FFT y centróide espectral
  // Copiamos vReal a arrays de FFT (ya en vReal, vImag=0)
  for (size_t i = 0; i < N_SAMPLES; i++) vImag[i] = 0.0;
  // Ventana Hanning para reducir leakage
  for (size_t i = 0; i < N_SAMPLES; i++) {
    double w = 0.5 * (1 - cos(2.0 * PI * i / (N_SAMPLES - 1)));
    vReal[i] = vReal[i] * w;
  }
  FFT.windowing(FFT_WIN_TYP_RECTANGLE, FFT_FORWARD); // ya aplicamos Hanning manual, mantenemos RECT
  FFT.compute(FFT_FORWARD);
  FFT.complexToMagnitude();

  double num = 0, den = 0;
  const double binHz = FS_HZ / N_SAMPLES;
  // Usamos bins 1..N/2-1 (excluye DC y Nyquist)
  for (size_t k = 1; k < N_SAMPLES/2; k++) {
    double f = k * binHz;
    double a = vReal[k];
    num += f * a;
    den += a + 1e-12;
  }
  double sc = num / den;

  Features feat;
  feat.rms = (float)rms;
  feat.crest_factor = (float)crest;
  feat.kurtosis = (float)kurt;
  feat.spectral_centroid = (float)sc;
  return feat;
}

// Z-score simple por campo
float zscore(float x, float mu, float sigma) {
  return (x - mu) / (sigma + 1e-9f);
}

void updateBaseline(const Features& f) {
  // Promedio incremental y desviación (Welford simplificado por campo)
  // Para robustez en demo, asumimos baselineStd constante aprox. y la recalculamos tras calentamiento.
  baselineWindows++;
  // Media incremental
  baselineMean.rms += (f.rms - baselineMean.rms) / baselineWindows;
  baselineMean.crest_factor += (f.crest_factor - baselineMean.crest_factor) / baselineWindows;
  baselineMean.kurtosis += (f.kurtosis - baselineMean.kurtosis) / baselineWindows;
  baselineMean.spectral_centroid += (f.spectral_centroid - baselineMean.spectral_centroid) / baselineWindows;

  // Tras WARMUP, podrías recalcular std con un buffer. Aquí, aproximamos tras estabilizar:
  if (baselineWindows == WARMUP_WINDOWS) {
    // Marcamos baseline ready; en práctica, acumula ventanas para std real.
    baselineStd = { baselineMean.rms * 0.1f + 1e-3f,
                    baselineMean.crest_factor * 0.1f + 1e-3f,
                    baselineMean.kurtosis * 0.1f + 1e-3f,
                    baselineMean.spectral_centroid * 0.1f + 1e-3f };
    baselineReady = true;
    Serial.println("Baseline listo.");
  }
}

// ---------------------- Payload ---------------------
// Estructura binaria compacta (little-endian)
// [0..3]   dev_id (uint32)   -> derivado de serial ATECC
// [4..7]   frameCounter (u32)
// [8..9]   q_rms (q15, *1000)
// [10..11] q_crest (q15, *1000)
// [12..13] q_kurt (q15, *1000)
// [14..15] q_sc (q15, *1)  (Hz, recortado/escala adecuada)
// [16]     flags (bit0=anomaly)
// Longitud: 17 bytes
struct __attribute__((packed)) Packet {
  uint32_t dev_id;
  uint32_t frame;
  int16_t q_rms;
  int16_t q_crest;
  int16_t q_kurt;
  int16_t q_sc;
  uint8_t flags;
};

uint32_t devIdFromATECC() {
  // Usa 4 bytes del serial (evita 0)
  uint32_t id = 0xA5A50000;
  if (ateccReady) {
    id = ((uint32_t)ateccSerial[5] << 24) |
         ((uint32_t)ateccSerial[6] << 16) |
         ((uint32_t)ateccSerial[7] << 8)  |
         ((uint32_t)ateccSerial[8]);
  }
  if (id == 0) id = 0xA5A5DEAD;
  return id;
}

// ---------------------- Setup/Loop ------------------
void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n[ESP32-S3] lora-predictive-vibration-icm42688");

  // I2C + IMU
  if (!initIMU()) {
    Serial.println("Fallo IMU. Corrige y reinicia.");
  }

  // ATECC608A
  ateccReady = initATECC();

  // LoRa
  if (!initLoRa()) {
    Serial.println("Fallo LoRa. Corrige y reinicia.");
  }

  // Sincroniza primer baseline
  baselineWindows = 0;
  baselineReady = false;
}

void loop() {
  // 1) Muestreo N_SAMPLES a ~FS_HZ usando lectura rápida del acelerómetro
  const float Ts_us = 1e6f / FS_HZ;
  uint32_t t0 = micros();
  for (size_t i = 0; i < N_SAMPLES; i++) {
    // Lectura de aceleración (g). Con hideakitai/ICM42688: imu.getAccelX/Y/Z()
    imu.update(); // refresca datos internamente
    float ax = imu.getAccelX(); // g
    // Puedes combinar magnitud o eje con mayor energía. Usamos |ax| centrado.
    vReal[i] = (double)ax; // g
    vImag[i] = 0.0;
    // Espera cronometrada
    uint32_t tNext = t0 + (uint32_t)((i + 1) * Ts_us);
    while ((int32_t)(micros() - tNext) < 0) { /* busy-wait corto */ }
  }

  // 2) Calcular features
  Features f = computeFeatures();

  // 3) Actualizar baseline (fase de calentamiento)
  if (!baselineReady) {
    updateBaseline(f);
  }

  // 4) Detección de anomalía simple (z-score combinado)
  float zr = zscore(f.rms, baselineMean.rms, baselineStd.rms);
  float zc = zscore(f.crest_factor, baselineMean.crest_factor, baselineStd.crest_factor);
  float zk = zscore(f.kurtosis, baselineMean.kurtosis, baselineStd.kurtosis);
  float zs = zscore(f.spectral_centroid, baselineMean.spectral_centroid, baselineStd.spectral_centroid);

  // Umbral combinado (p. ej., cualquier |z| > 3)
  bool anomaly = baselineReady && (fabs(zr) > 3.0f || fabs(zc) > 3.0f || fabs(zk) > 3.0f || fabs(zs) > 3.0f);

  // 5) Construir y enviar payload LoRa
  Packet pkt;
  pkt.dev_id = devIdFromATECC();
  pkt.frame = frameCounter++;
  pkt.q_rms   = q15_from_float(f.rms, 1000.0f);           // g -> mg
  pkt.q_crest = q15_from_float(f.crest_factor, 1000.0f);  // adim
  // limita kurtosis razonable (0..20)
  float kurt_limited = fminf(fmaxf(f.kurtosis, 0.0f), 20.0f);
  pkt.q_kurt  = q15_from_float(kurt_limited, 1000.0f);
  // centróide en Hz, suponiendo < 1600 Hz -> escala 1
  float sc_limited = fminf(fmaxf(f.spectral_centroid, 0.0f), 1600.0f);
  pkt.q_sc    = q15_from_float(sc_limited, 1.0f);
  pkt.flags   = (anomaly ? 0x01 : 0x00);

  int16_t state = radio.transmit((uint8_t*)&pkt, sizeof(pkt));
  if (state == RADIOLIB_ERR_NONE) {
    Serial.printf("#%lu LoRa TX OK | RMS=%.4f g, CF=%.3f, K=%.3f, SC=%.1f Hz | anomaly=%d\n",
      (unsigned long)pkt.frame, f.rms, f.crest_factor, f.kurtosis, f.spectral_centroid, anomaly);
  } else {
    Serial.printf("LoRa TX fallo, code %d\n", state);
  }

  // 6) Respeta duty-cycle (ej., 1 envío por segundo aprox.)
  delay(1000);
}

Explicación breve de partes clave:
– Configuración de pines/buses: se fija I2C en SDA=8, SCL=9; y SPI en SCK=12/MISO=13/MOSI=11/CS=10, con RST y DIOs para el RFM95W.
– IMU: se configura ODR de 1.6 kHz y se habilita acelerómetro con LPF ~180 Hz para capturar vibraciones relevantes (puedes modificar ODR/LPF según tu máquina).
– Muestreo: se adquiere N_SAMPLES=512 puntos a ~1.6 kHz (≈320 ms de ventana).
– Features: se calculan RMS, crest factor, kurtosis (dominio del tiempo) y centróide espectral (dominio de frecuencia mediante FFT).
– Baseline: primeras 20 ventanas estiman medias y desviaciones, para luego calcular z-scores.
– Anomalía: gatilla si cualquier |z| > 3.
– ATECC608A: se lee el número de serie para derivar un dev_id estable. Si ya tienes el ATECC provisionado con clave, puedes firmar el hash del payload antes de transmitir (se deja fuera por simplicidad operativa).
– LoRa: RadioLib facilita setear frecuencia, BW=125 kHz, SF=9 (ajusta según SNR/datarate necesarios).

Compilación, flash y ejecución

1) Instala PlatformIO Core (CLI) si aún no lo tienes:
– Windows:
– pipx install platformio o py -m pip install –user platformio
– Ubuntu/macOS:
– python3 -m pip install –user platformio
– Verifica:
– pio –version → debería mostrar “PlatformIO Core, Home, Python…”

2) Crea el proyecto:
– pio project init –board esp32-s3-devkitc-1
– Sustituye el platformio.ini generado por el que se muestra arriba.
– Crea src/main.cpp y pega el código anterior.

3) Conecta la ESP32-S3-DevKitC-1 por USB-C. En Linux:
– ls /dev/ttyACM* → debería ver /dev/ttyACM0
– Si no, agrega permisos: sudo usermod -a -G dialout $USER y relogin.

4) Compila:
– pio run

5) Sube firmware:
– pio run -t upload
– Si necesitas especificar puerto: pio run -t upload -e esp32-s3-devkitc-1 –upload-port /dev/ttyACM0

6) Abre monitor serie:
– pio device monitor –baud 115200
– Si el puerto cambia tras reset, especifica: pio device monitor –port /dev/ttyACM0 –baud 115200

7) Salida esperada al iniciar:
– [ESP32-S3] lora-predictive-vibration-icm42688
– ICM-42688-P inicializado.
– ATECC608A S/N: XXXXXXX…
– SX1276 (RFM95W) inicializado.
– Baseline listo. (tras ~20 ventanas)

Validación paso a paso

1) Validación de hardware (sin firmware):
– Verifica continuidad GND común.
– Verifica 3V3 estable en VCC de RFM95W, ICM-42688-P y ATECC608A.
– Comprueba la dirección I2C con un escáner simple (opcional): debería ver 0x68 y 0x60.

2) Validación de inicialización:
– En el monitor serie, confirma los mensajes:
– “ICM-42688-P inicializado.”
– “ATECC608A S/N: …”
– “SX1276 (RFM95W) inicializado.”
– Si falla alguno, revisa el apartado Troubleshooting.

3) Validación de muestreo/FFT:
– Con el dispositivo inmóvil, observa:
– RMS bajo (p. ej., < 0.02 g), centróide ~ muy bajo (ruido).
– Kurtosis cerca de 3 (gaussiano ideal).
– Induce vibración (pe. motor con desequilibrio, golpe suave):
– RMS sube.
– Crest factor sube si hay picos.
– Centróide se desplaza hacia la frecuencia dominante.

4) Validación de baseline/anomalía:
– Tras “Baseline listo.”, repite la inducción de vibración:
– Observa anomaly=1 cuando el z-score supera el umbral.
– Si no se gatilla, incrementa sensibilidad (reduce baselineStd o baja umbral a 2.5).

5) Validación de LoRa TX:
– En el monitor serie: “LoRa TX OK” por cada frame.
– Con un receptor LoRa (opcional), configura la misma modulación (BW=125 kHz, SF=9, CR=4/5, SyncWord=0x34, frecuencia regional) y captura payload de 17 bytes.
– Decodifica el payload (dev_id, frame, q_rms mg, q_crest1e-3, q_kurt1e-3, q_sc Hz, flags).
– Si no tienes receptor, al menos confirma que la transmisión no da error y que el LED en el RFM95W (si existe) muestra actividad RF (no todos los módulos tienen LED).

6) Validación de ATECC:
– Verifica que el S/N impreso permanece constante entre reinicios.
– Opcional: prueba una llamada atcab_random() y muestra 32 bytes aleatorios (para confirmar crypto HAL). No usado en el payload base.

7) Medidas cuantitativas de referencia:
– Inmóvil:
– RMS ~ 0.005–0.02 g
– CF ~ 1.2–2.0
– K ~ 2.5–3.5
– SC ~ < 50 Hz
– Vibrando:
– RMS > 0.05 g (según condición)
– CF puede subir a 3–8 si hay impactos.
– SC tiende a la frecuencia de vibración (100–300 Hz típico en pequeños motores, o acorde a tu excitación).

Troubleshooting

1) No aparece puerto serie / no sube el firmware:
– Revisa cable USB (que soporte datos).
– Prueba otro puerto USB del equipo.
– Mantén pulsado BOOT al reset para forzar modo descarga (si aplica).
– En Linux: agrega tu usuario a dialout; asegura permisos en /dev/ttyACM*.
– En macOS, cierra apps que capturan el puerto (p. ej., otro monitor serie).

2) “ICM-42688-P no responde en 0x68”:
– Revisa SDA=GPIO8, SCL=GPIO9, VCC=3V3, GND.
– Asegúrate de que AD0 está a GND si usas 0x68 (o cambia la dirección en el código a 0x69).
– Verifica que Wire.begin(I2C_SDA, I2C_SCL, 400000) no sea llamado dos veces con otros pines.
– Baja la velocidad I2C a 100 kHz en casos de cables largos.

3) ATECC init fallo (códigos – falla en atcab_init o read serial):
– Confirma SDA/SCL compartidos correctamente con IMU.
– Verifica dirección 0x60.
– Algunos breakouts requieren pull-ups a 3.3V (4.7 kΩ). Asegura que existan (al menos en un dispositivo del bus).
– Si persiste, prueba a inicializarlo antes del IMU (orden de init a veces afecta en bus compartido).

4) LoRa init fallo, code -2/-5 (RadioLib):
– Verifica cableado SPI (SCK=12, MISO=13, MOSI=11, CS=10, RST=18, DIO0=17).
– Asegura que SPI.begin() usa esos pines.
– Revisa que VCC=3.3V (no 5V) y consumo suficiente (≈120 mA en TX).
– Cambia la frecuencia a la adecuada para tu módulo/región (p. ej., 915.0 para US).

5) LoRa TX fallo al transmitir:
– Reduce potencia (radio.setOutputPower(10)) si tu alimentación es marginal.
– Aumenta preámbulo (radio.setPreambleLength(12)) para robustez.
– Asegura antena adecuada a la banda (SWR razonable).

6) Lecturas de IMU ruidosas o saturadas:
– Baja el rango a ±4 g si saturas menos (setAccelRange(g4)) o súbelo a ±16 g si saturas por impactos.
– Ajusta LPF (100–180 Hz típicos, según vibración de interés).
– Monta mecánicamente la IMU con tornillos o cinta de espuma para evitar resonancias parásitas.

7) Baseline nunca “listo” o demasiados falsos positivos:
– Aumenta WARMUP_WINDOWS (p. ej., 50).
– Calcula std real con un buffer deslizante (mejora sugerida abajo).
– Subir umbral a 3.5 o 4 si hay demasiado ruido.

8) El monitor serie no muestra nada:
– Asegura build_flags con USB CDC on boot.
– Cambia monitor_port al dispositivo correcto (Windows: COMx; Linux: /dev/ttyACM0).
– Pulsa RESET y reabre monitor.

Mejoras/variantes

  • LoRaWAN (The Things Network):
  • Sustituir RadioLib por una pila LoRaWAN (p. ej., mcci-catena LMIC o RadioLib LoRaWAN si soportado).
  • ATECC608A para almacenar AppKey/NwkKey y hacer join seguro.
  • Cuidado con duty-cycle y tamaño de payload (codificar features de manera compacta).

  • Mejor baseline/anomalía:

  • Mantener un buffer circular de M ventanas y calcular media/desviación robustas (mediana/MAD).
  • Modelado espectral: comparar picos dominantes y su desplazamiento a lo largo del tiempo.
  • One-class SVM o autoencoders con cuantización fija (TinyML). EloquentTinyML o TFLM si te mantienes en 20–30 kB de RAM para el modelo.

  • Ampliación de características:

  • Band energy ratios (BER) para bandas [0–100, 100–300, 300–600 Hz…].
  • Envolvente Hilbert o demodulación para vibración de rodamientos.
  • Kurtosis espectral, skewness, picos armónicos.

  • ATECC608A: autenticidad e integridad:

  • Firmar SHA-256 del payload con atcab_sign() si la clave privada está provisionada en Slot 0.
  • En el receptor, validar con la clave pública correspondiente.
  • Añadir un nonce/challenge para evitar repetición.

  • Gestión de energía:

  • Dormir entre ventanas y despertar con temporizador.
  • Ajustar potencia LoRa y SF según calidad de enlace medido.

  • Robustez RF:

  • Saltos de frecuencia (FHSS simple a nivel de aplicación).
  • Retransmisión con ACK por un receptor si implementas enlace a medida.

  • Calibración y alineación:

  • Compensación de gravedad (restar media DC por eje).
  • Montaje con orientación conocida (usar combinaciones ax/ay/az o magnitud sqrt).

Checklist de verificación

  • [ ] He instalado PlatformIO Core 6.1.15 y verificado pio –version.
  • [ ] platformio.ini usa platform = espressif32 @ 6.6.0 y framework-arduinoespressif32 @ 3.0.7.
  • [ ] He cableado I2C en SDA=GPIO8, SCL=GPIO9 para ICM-42688-P (0x68) y ATECC608A (0x60).
  • [ ] He cableado SPI LoRa con SCK=12, MISO=13, MOSI=11, CS=10, RST=18, DIO0=17, DIO1=16.
  • [ ] Compila sin errores: pio run OK.
  • [ ] Carga sin errores: pio run -t upload OK.
  • [ ] Monitor serie muestra inicializaciones correctas (IMU, ATECC S/N, LoRa).
  • [ ] Tras 20 ventanas, aparece “Baseline listo.”.
  • [ ] Induciendo vibración, veo RMS/CF/SC aumentar y a veces anomaly=1.
  • [ ] Veo “LoRa TX OK” por frame (1 Hz aprox.).
  • [ ] (Opcional) Un receptor LoRa con misma modulación ve payloads de 17 bytes coherentes.

Notas finales:
– Este caso práctico mantiene coherencia total con el hardware “ESP32-S3-DevKitC-1 + RFM95W (SX1276) + ICM-42688-P + ATECC608A”, en materiales, conexiones, código y comandos.
– La cadena de herramientas exacta y las versiones fijadas garantizan reproducibilidad. Si alguna versión cambia, mantén los mismos números en platformio.ini para evitar variaciones del entorno.
– Ajusta la frecuencia LoRa a tu región y respeta la normativa local (duty-cycle, potencia, bandas).

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué versión de Python se debe utilizar en Windows?




Pregunta 2: ¿Cuál es la versión de PlatformIO Core que se debe usar?




Pregunta 3: ¿Qué librería se utiliza para el manejo de SX1276/RFM95W?




Pregunta 4: ¿Qué plataforma se requiere para PlatformIO?




Pregunta 5: ¿Cuál es la versión del framework de Arduino para ESP32?




Pregunta 6: ¿Qué controlador USB se necesita si se usa un adaptador USB-UART alternativo en Windows?




Pregunta 7: ¿Qué comando se utiliza para verificar la versión de PlatformIO Core?




Pregunta 8: ¿Qué debe hacerse en Linux para acceder a /dev/ttyACM*?




Pregunta 9: ¿Qué tipo de USB utiliza la ESP32-S3-DevKitC-1 para programarse?




Pregunta 10: ¿Cuál es la versión del adaptador CH34x para Windows/macOS?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
error: Contenido Protegido / Content is protected !!
Scroll to Top