You dont have javascript enabled! Please enable it!

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óduloSeñalPin en móduloPin en ESP32-S3-DevKitC-1
RFM95W (SX1276)VCC3V33V3
RFM95W (SX1276)GNDGNDGND
RFM95W (SX1276)SCKSCKGPIO 12
RFM95W (SX1276)MISOMISOGPIO 13
RFM95W (SX1276)MOSIMOSIGPIO 11
RFM95W (SX1276)NSS (CS)NSSGPIO 10
RFM95W (SX1276)RSTRSTGPIO 18
RFM95W (SX1276)DIO0DIO0GPIO 17
RFM95W (SX1276)DIO1 (opcional)DIO1GPIO 16
ICM-42688-PVCC3V33V3
ICM-42688-PGNDGNDGND
ICM-42688-PSDASDAGPIO 8
ICM-42688-PSCLSCLGPIO 9
ICM-42688-PAD0AD0 a GND (0x68)
ATECC608AVCC3V33V3
ATECC608AGNDGNDGND
ATECC608ASDASDAGPIO 8
ATECC608ASCLSCLGPIO 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:
Scroll al inicio