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
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.



