Caso práctico: FFT vibraciones RS485 con Arduino y ADXL355

Caso práctico: FFT vibraciones RS485 con Arduino y ADXL355 — hero

Objetivo y caso de uso

Qué construirás: Un monitor de vibraciones FFT robusto utilizando Arduino Mega 2560 y ADXL355 para la transmisión de datos en tiempo real a través de RS-485.

Para qué sirve

  • Monitoreo de vibraciones en maquinaria industrial para detectar fallos.
  • Control de calidad en procesos de manufactura mediante análisis de vibraciones.
  • Aplicaciones en mantenimiento predictivo para evitar paradas no programadas.
  • Integración en sistemas de automatización para la supervisión remota de equipos.

Resultado esperado

  • Transmisión de datos de vibración en tiempo real con latencias menores a 100 ms.
  • Frecuencia de muestreo de vibraciones a 1 kHz para análisis FFT.
  • Mensajes de alerta enviados a través de RS-485 al detectar vibraciones anómalas.
  • Capacidad de enviar hasta 10 paquetes de datos por segundo a través de la red.

Público objetivo: Ingenieros y técnicos en automatización; Nivel: Avanzado

Arquitectura/flujo: Arduino Mega 2560 <-> ADXL355 <-> W5500 <-> RS-485

Nivel: Avanzado

Prerrequisitos

Sistema operativo y herramientas

  • Sistema operativo base (elige uno y mantén coherencia):
  • Linux: Ubuntu 22.04 LTS (Jammy) x86_64
  • Toolchain de Arduino (CLI, no GUI):
  • Arduino CLI v0.35.3 (linux-amd64)
  • Core AVR: arduino:avr@1.8.6
  • Librerías Arduino:
    • Ethernet@2.0.2 (para W5500)
    • arduinoFFT@1.6.0
    • SPI (incluida en el core)
  • Python 3.10 (para validación opcional) con:
  • pyserial==3.5
  • Adaptador USB–RS485 (para validación del bus RS485)

Permisos y preparación del entorno (Linux)

  • Añade tu usuario a dialout para acceso serie:
  • sudo usermod -aG dialout «$USER»
  • Cierra sesión y vuelve a entrar.
  • Directorio de trabajo limpio (por ejemplo, $HOME/proyectos/fft-vibration-monitor-rs485).

Red local

  • Red IPv4 básica con rango 192.168.1.0/24 (o adapta IP estática en el código).
  • Sin servidor DHCP estrictamente necesario si usas IP estática.

Materiales

  • Placa principal: Arduino Mega 2560 (ATmega2560).
  • Shield de red: Ethernet Shield W5500 (compatibilidad Arduino oficial, CS en D10).
  • Acelerómetro triaxial: ADXL355 (interfaz SPI, alimentación 3.3 V).
  • Transceptor RS485: MAX485 (modo half-duplex).
  • Nivelador de lógica bidireccional 5 V ↔ 3.3 V para SPI del ADXL355 (p. ej., TXB0104 o módulo BSS138 de 4 canales).
  • Resistencias y pasivos:
  • Terminación RS485: 120 Ω (colocar en el extremo de la línea, cerca del MAX485 si es fin de línea).
  • Resistencias de polarización (bias) RS485 en el bus (si tu red no las tiene): típicamente 680 Ω–1 kΩ entre A–Vcc y B–GND en un único punto.
  • Fuente de alimentación estable 5 V para Arduino (USB o externa) y 3.3 V para el ADXL355 (puede provenir del 3.3 V del Mega o del Shield; verificar capacidad de corriente).
  • Cables Dupont y cable par trenzado para la línea RS485 (A/B).
  • Adaptador USB–RS485 para el PC (para validación).
  • Opcional: base o soporte para el sensor y un pequeño motor o vibrador para generar vibraciones reproducibles.

Nota: El conjunto es exactamente “Arduino Mega 2560 + Ethernet Shield W5500 + ADXL355 + MAX485” y toda la guía asume estos cuatro elementos.

Preparación y conexión

Reglas generales de cableado

  • Mantén GND común entre todos los módulos.
  • El ADXL355 es 3.3 V-only. Nunca apliques 5 V a sus pines de lógica. Usa nivelador para MOSI, SCK y CS. La línea MISO del ADXL355 a 3.3 V suele ser interpretada como HIGH por el Mega, pero es buena práctica encaminarla a través del nivelador si el módulo lo requiere.
  • Todos los dispositivos SPI comparten SCK/MOSI/MISO; cada uno debe tener su propia línea CS (Chip Select). Asegúrate de poner en HIGH los CS de los dispositivos que no estés usando en cada transacción.
  • El Ethernet Shield W5500 usa el bus SPI por el conector ICSP y CS en D10. La SD del shield usa CS en D4 (mantenla en HIGH si no se usa).
  • RS485 (MAX485) es half-duplex: controla las líneas DE/RE con un pin digital para alternar transmisión/recepción.

Mapa de pines y conexiones

Tabla de cableado resumido:

Módulo Señal Arduino Mega 2560 Notas
W5500 (Shield) SPI ICSP (SCK/MISO/MOSI) Se conecta por el header ICSP del Shield
W5500 (Shield) CS D10 Mantener HIGH cuando SPI se use con otros dispositivos
W5500 (Shield) SD CS D4 Mantener HIGH si no se usa la SD
ADXL355 (SPI) VCC 3.3 V Alimentación 3.3 V
ADXL355 (SPI) GND GND Tierra común
ADXL355 (SPI) SCK D52 (SCK) ↔ nivelador SPI compartido, va al nivelador hacia el sensor
ADXL355 (SPI) MOSI D51 (MOSI) ↔ nivelador SPI compartido, 5 V→3.3 V
ADXL355 (SPI) MISO D50 (MISO) (3.3 V) 3.3 V suele ser aceptado; opcional nivelador
ADXL355 (SPI) CS D7 ↔ nivelador CS dedicado para el ADXL355
ADXL355 (INT) DRDY D3 (INT1) Señal de “data ready” (opcional pero recomendable)
MAX485 VCC 5 V Alimentación del transceptor
MAX485 GND GND Tierra común
MAX485 RO (Receiver Out) D19 (RX1) UART1 RX del Mega
MAX485 DI (Driver In) D18 (TX1) UART1 TX del Mega
MAX485 /RE y DE D2 (control) Une /RE y DE, controla con D2
MAX485 A/B Línea RS485 Conectar a bus y poner 120 Ω si eres extremo
Arduino Mega USB PC Para cargar firmware y depurar por Serial

Notas avanzadas:
– Si usas DRDY del ADXL355, podrás muestrear con jitter mínimo y exactitud de ODR (muy recomendable para FFT).
– Mantén los cables SPI cortos y ordenados para reducir EMI.
– Coloca el ADXL355 firmemente sobre la estructura cuyas vibraciones deseas medir (acoplamiento mecánico firme).

Código completo (C++ para Arduino Mega 2560)

A continuación, un sketch monolítico que:
– Inicializa W5500 con IP estática.
– Inicializa el ADXL355 en SPI (modo medición, rango ±2 g).
– Toma 256 muestras a 1 kHz del eje Z (opcionalmente por DRDY).
– Calcula FFT con arduinoFFT y obtiene picos dominantes.
– Expone los resultados por RS485 (comandos de texto) y por HTTP (endpoint /status).
– Evita conflictos SPI con selección adecuada de CS.

Características del protocolo RS485:
– Velocidad: 115200 8N1 en Serial1 (pines 18/19).
– Control de dirección (D2): HIGH para transmitir, LOW para recibir.
– Comandos (terminados en ‘
‘):
– ID?
– GET:PEAKS
– GET:RMS
– GET:FFT (devuelve magnitudes de N/2 bins como CSV reducido, opcional)

Bloque 1/2 – Sketch principal:

/*
  fft-vibration-monitor-rs485.ino
  Dispositivo: Arduino Mega 2560 + Ethernet Shield W5500 + ADXL355 + MAX485
  Toolchain: Arduino CLI v0.35.3, core arduino:avr@1.8.6
  Librerías: Ethernet@2.0.2, arduinoFFT@1.6.0, SPI (core)
*/

#include <SPI.h>
#include <Ethernet.h>
#include <arduinoFFT.h>

// ------------------------ Configuración de pines ------------------------
static const uint8_t PIN_CS_W5500   = 10; // CS Ethernet
static const uint8_t PIN_CS_SD      = 4;  // CS SD en el Shield
static const uint8_t PIN_CS_ADXL    = 7;  // CS del ADXL355
static const uint8_t PIN_ADXL_DRDY  = 3;  // DRDY -> INT1 (opcional)
static const uint8_t PIN_RS485_DIR  = 2;  // DE y /RE del MAX485 unidos -> D2

// ------------------------ Red (Ethernet W5500) --------------------------
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 };
IPAddress ip(192, 168, 1, 177);
EthernetServer server(80);

// ------------------------ ADXL355 (SPI) ---------------------------------
// Registro y constantes (ver datasheet ADXL355)
#define ADXL355_REG_DEVID_AD   0x00
#define ADXL355_REG_DEVID_MST  0x01
#define ADXL355_REG_PARTID     0x02
#define ADXL355_REG_REVID      0x03
#define ADXL355_REG_STATUS     0x04
#define ADXL355_REG_TEMP2      0x06
#define ADXL355_REG_TEMP1      0x07
#define ADXL355_REG_XDATA3     0x08
#define ADXL355_REG_XDATA2     0x09
#define ADXL355_REG_XDATA1     0x0A
#define ADXL355_REG_YDATA3     0x0B
#define ADXL355_REG_YDATA2     0x0C
#define ADXL355_REG_YDATA1     0x0D
#define ADXL355_REG_ZDATA3     0x0E
#define ADXL355_REG_ZDATA2     0x0F
#define ADXL355_REG_ZDATA1     0x10
#define ADXL355_REG_FILTER     0x28
#define ADXL355_REG_RANGE      0x2C
#define ADXL355_REG_POWER_CTL  0x2D
#define ADXL355_REG_RESET      0x2F

// Modo SPI: CPOL=0, CPHA=0 (Mode 0), MSB first
SPISettings spiADXL(5000000, MSBFIRST, SPI_MODE0); // 5 MHz (ajustable)

// Escala (LSB/g) aproximada del ADXL355 en ±2g (ver datasheet)
static const float ADXL355_LSB_PER_G = 256000.0f;

// ------------------------ FFT y muestreo --------------------------------
static const uint16_t FS_HZ      = 1000;  // Frecuencia de muestreo efectiva
static const uint16_t N_SAMPLES  = 256;   // Longitud de la FFT (potencia de 2)
static const float    INV_FS     = 1.0f / FS_HZ;

double vReal[N_SAMPLES];
double vImag[N_SAMPLES];

arduinoFFT FFT = arduinoFFT(vReal, vImag, N_SAMPLES, FS_HZ);

// Buffer de adquisición
volatile uint16_t sampleIndex = 0;
volatile bool bufferReady = false;
volatile float bufZ[N_SAMPLES]; // Aceleración (g) eje Z

// Métricas
volatile float lastRMS = 0.0f;
volatile float lastPeakFreq = 0.0f;

// Resultados de picos (para respuesta)
static const uint8_t NUM_TOP_PEAKS = 8;
float topFreq[NUM_TOP_PEAKS];
float topMag[NUM_TOP_PEAKS];

// ------------------------ Utilidades SPI/CS -----------------------------
inline void csHighAll() {
  digitalWrite(PIN_CS_W5500, HIGH);
  digitalWrite(PIN_CS_SD, HIGH);
  digitalWrite(PIN_CS_ADXL, HIGH);
}

uint8_t adxl355_read8(uint8_t reg) {
  uint8_t val;
  csHighAll();
  digitalWrite(PIN_CS_ADXL, LOW);
  SPI.beginTransaction(spiADXL);
  // Lectura: bit 7 = 1 indica lectura, dirección en bits 6..0
  SPI.transfer(0x80 | (reg & 0x7F));
  val = SPI.transfer(0x00);
  SPI.endTransaction();
  digitalWrite(PIN_CS_ADXL, HIGH);
  return val;
}

void adxl355_write8(uint8_t reg, uint8_t val) {
  csHighAll();
  digitalWrite(PIN_CS_ADXL, LOW);
  SPI.beginTransaction(spiADXL);
  // Escritura: bit 7 = 0
  SPI.transfer(reg & 0x7F);
  SPI.transfer(val);
  SPI.endTransaction();
  digitalWrite(PIN_CS_ADXL, HIGH);
}

int32_t adxl355_read20(uint8_t regMSB) {
  // Lee 20 bits firmados (en 3 bytes, donde los 4 bits LSB del tercer byte son significativos)
  uint8_t b3, b2, b1;
  int32_t raw = 0;
  csHighAll();
  digitalWrite(PIN_CS_ADXL, LOW);
  SPI.beginTransaction(spiADXL);
  SPI.transfer(0x80 | (regMSB & 0x7F)); // dirección de XDATA3/YDATA3/ZDATA3
  b3 = SPI.transfer(0x00);
  b2 = SPI.transfer(0x00);
  b1 = SPI.transfer(0x00);
  SPI.endTransaction();
  digitalWrite(PIN_CS_ADXL, HIGH);

  raw = ((int32_t)b3 << 12) | ((int32_t)b2 << 4) | ((b1 >> 4) & 0x0F);
  // Extensión de signo de 20 bits
  if (raw & 0x80000) {
    raw |= 0xFFF00000;
  }
  return raw;
}

bool adxl355_init() {
  // Verifica IDs
  uint8_t devid_ad  = adxl355_read8(ADXL355_REG_DEVID_AD);
  uint8_t devid_mst = adxl355_read8(ADXL355_REG_DEVID_MST);
  uint8_t partid    = adxl355_read8(ADXL355_REG_PARTID);
  // Valores típicos esperados: 0xAD, 0x1D, 0xED
  if (devid_ad != 0xAD || devid_mst != 0x1D || partid != 0xED) {
    return false;
  }

  // Reset suave (opcional)
  adxl355_write8(ADXL355_REG_RESET, 0x52); // Key 'R'

  delay(20);

  // Standby para configurar (bit 0 de POWER_CTL = 0)
  // Según datasheet, POWER_CTL[0]=0 -> Standby, [0]=1 -> Measurement
  uint8_t pwr = adxl355_read8(ADXL355_REG_POWER_CTL);
  pwr &= ~0x01; // asegurar Standby
  adxl355_write8(ADXL355_REG_POWER_CTL, pwr);

  // Rango ±2g (ver datasheet: RANGE bits 1:0 seleccionan rango)
  // 0x01: ±2g (según hoja de datos; validar si tu módulo usa diferente mapeo)
  adxl355_write8(ADXL355_REG_RANGE, 0x01);

  // ODR 1000 Hz (aprox.). En ADXL355_REG_FILTER, bits 3:0 seleccionan ODR/LPF.
  // Un valor típico para ~1 kHz es 0x05 (consultar tablas en hoja de datos).
  // Ajusta si necesitas ODR preciso.
  adxl355_write8(ADXL355_REG_FILTER, 0x05);

  // Measurement mode
  pwr = adxl355_read8(ADXL355_REG_POWER_CTL);
  pwr |= 0x01; // bit 0 a 1 -> Measurement
  adxl355_write8(ADXL355_REG_POWER_CTL, pwr);

  delay(10);
  return true;
}

float adxl355_readZ_g() {
  int32_t raw = adxl355_read20(ADXL355_REG_ZDATA3);
  // Conversión a g (aprox.)
  return ((float)raw) / ADXL355_LSB_PER_G;
}

// ------------------------ RS485 (Serial1) -------------------------------
void rs485_setRx() { digitalWrite(PIN_RS485_DIR, LOW);  }
void rs485_setTx() { digitalWrite(PIN_RS485_DIR, HIGH); }
void rs485_println(const String &s) {
  rs485_setTx();
  Serial1.print(s);
  Serial1.print('\n');
  Serial1.flush();
  rs485_setRx();
}

// ------------------------ Temporización de muestreo ---------------------
void setupTimer1_1kHz() {
  // Timer1 CTC a 1 kHz: f_clk = 16 MHz, prescaler 1, OCR1A = 15999
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A = 15999; // 16e6 / 1e3 - 1
  TCCR1B |= (1 << WGM12); // CTC
  TCCR1B |= (1 << CS10);  // prescaler 1
  TIMSK1 |= (1 << OCIE1A);
  interrupts();
}

ISR(TIMER1_COMPA_vect) {
  if (bufferReady) return; // espera a que procesen
  // Lectura directa del eje Z a ~1 kHz
  float z_g = adxl355_readZ_g();
  bufZ[sampleIndex] = z_g;
  sampleIndex++;
  if (sampleIndex >= N_SAMPLES) {
    sampleIndex = 0;
    bufferReady = true;
  }
}

// ------------------------ Procesamiento FFT -----------------------------
void computeFFTAndMetrics() {
  // Copiar el buffer a vReal/vImag y aplicar ventana Hann
  for (uint16_t i = 0; i < N_SAMPLES; i++) {
    double w = 0.5 * (1.0 - cos(2.0 * PI * i / (N_SAMPLES - 1)));
    vReal[i] = (double)bufZ[i] * w;
    vImag[i] = 0.0;
  }

  // RMS (dominio tiempo)
  double sum2 = 0.0;
  for (uint16_t i = 0; i < N_SAMPLES; i++) sum2 += vReal[i] * vReal[i];
  lastRMS = sqrt(sum2 / N_SAMPLES);

  // FFT
  FFT.windowing(vReal, N_SAMPLES, FFT_WIN_TYP_RECTANGLE, FFT_FORWARD); // ya aplicamos Hann, pero dejamos sin ventana aquí
  FFT.compute(vReal, vImag, N_SAMPLES, FFT_FORWARD);
  FFT.complexToMagnitude(vReal, vImag, N_SAMPLES);

  // Encontrar picos en 0..Fs/2
  // Ignora bin 0 (DC)
  uint16_t startBin = 1;
  uint16_t endBin = (N_SAMPLES / 2) - 1;

  // Inicializa arrays de top N picos
  for (uint8_t k = 0; k < NUM_TOP_PEAKS; k++) {
    topFreq[k] = 0.0f;
    topMag[k]  = 0.0f;
  }

  // Búsqueda simple de picos
  double maxMag = 0.0;
  uint16_t maxBin = 0;

  for (uint16_t bin = startBin; bin <= endBin; bin++) {
    double mag = vReal[bin];
    // Peak global
    if (mag > maxMag) {
      maxMag = mag;
      maxBin = bin;
    }
    // Inserción ordenada en top N
    for (uint8_t k = 0; k < NUM_TOP_PEAKS; k++) {
      if (mag > topMag[k]) {
        // Desplaza hacia abajo
        for (int8_t j = NUM_TOP_PEAKS - 1; j > (int8_t)k; j--) {
          topMag[j]  = topMag[j - 1];
          topFreq[j] = topFreq[j - 1];
        }
        topMag[k]  = mag;
        topFreq[k] = (float)bin * ((float)FS_HZ / (float)N_SAMPLES);
        break;
      }
    }
  }

  lastPeakFreq = (float)maxBin * ((float)FS_HZ / (float)N_SAMPLES);
}

// ------------------------ HTTP /status ----------------------------------
void handleHttpClient(EthernetClient &client) {
  // Lectura simple de la primera línea
  String req = client.readStringUntil('\n');
  if (req.indexOf("GET /status") >= 0 || req.indexOf("GET / ") >= 0) {
    // Respuesta JSON simple
    String body = "{";
    body += "\"device\":\"fft-vibration-monitor-rs485\",";
    body += "\"board\":\"Arduino Mega 2560\",";
    body += "\"fs\":" + String(FS_HZ) + ",";
    body += "\"n\":" + String(N_SAMPLES) + ",";
    body += "\"rms_g\":" + String(lastRMS, 6) + ",";
    body += "\"peak_freq_hz\":" + String(lastPeakFreq, 2) + ",";
    body += "\"top_peaks\":[";
    for (uint8_t k = 0; k < NUM_TOP_PEAKS; k++) {
      body += "{\"f\":" + String(topFreq[k], 2) + ",\"a\":" + String(topMag[k], 6) + "}";
      if (k < NUM_TOP_PEAKS - 1) body += ",";
    }
    body += "]";
    body += "}\n";

    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/json");
    client.print("Content-Length: ");
    client.println(body.length());
    client.println("Connection: close");
    client.println();
    client.print(body);
  } else {
    client.println("HTTP/1.1 404 Not Found");
    client.println("Content-Length: 0");
    client.println("Connection: close");
    client.println();
  }
}

// ------------------------ Comandos RS485 --------------------------------
String cmdBuf;
void handleRS485() {
  while (Serial1.available() > 0) {
    char c = (char)Serial1.read();
    if (c == '\r') continue;
    if (c == '\n') {
      String line = cmdBuf;
      cmdBuf = "";
      line.trim();
      if (line == "ID?") {
        rs485_println("ID,ArduinoMega2560,ADXL355,W5500,MAX485");
      } else if (line == "GET:RMS") {
        rs485_println("RMS_G," + String(lastRMS, 6));
      } else if (line == "GET:PEAKS") {
        // Responde pares f,a separados por punto y coma
        String resp = "PEAKS";
        for (uint8_t k = 0; k < NUM_TOP_PEAKS; k++) {
          resp += ",";
          resp += String(topFreq[k], 2);
          resp += ",";
          resp += String(topMag[k], 6);
        }
        rs485_println(resp);
      } else if (line == "GET:FFT") {
        // Envía magnitudes de 0..N/2-1 (corta si quieres ahorrar ancho de banda)
        rs485_println("FFT,Fs=" + String(FS_HZ) + ",N=" + String(N_SAMPLES));
        String row = "";
        for (uint16_t bin = 0; bin < (N_SAMPLES / 2); bin++) {
          row += String(vReal[bin], 6);
          if (bin < (N_SAMPLES / 2) - 1) row += ",";
        }
        rs485_println(row);
      } else {
        rs485_println("ERR,UNKNOWN_CMD");
      }
    } else {
      if (cmdBuf.length() < 128) cmdBuf += c;
    }
  }
}

// ------------------------ Setup / Loop ----------------------------------
void setup() {
  pinMode(PIN_CS_W5500, OUTPUT);
  pinMode(PIN_CS_SD, OUTPUT);
  pinMode(PIN_CS_ADXL, OUTPUT);
  pinMode(PIN_RS485_DIR, OUTPUT);
  pinMode(PIN_ADXL_DRDY, INPUT); // opcional si se conecta DRDY

  csHighAll();
  rs485_setRx();

  Serial.begin(115200);   // Depuración por USB
  Serial1.begin(115200);  // RS485 (MAX485)

  // SPI
  SPI.begin();

  // Ethernet
  Ethernet.init(PIN_CS_W5500);
  Ethernet.begin(mac, ip);
  delay(100);
  server.begin();

  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());

  // Inicializa ADXL355
  if (!adxl355_init()) {
    Serial.println("Error: ADXL355 no detectado (IDs no coinciden).");
  } else {
    Serial.println("ADXL355 OK");
  }

  // Timer de muestreo (1 kHz)
  setupTimer1_1kHz();

  Serial.println("Setup completo.");
}

void loop() {
  // Procesar buffer si listo
  if (bufferReady) {
    noInterrupts();
    bufferReady = false;
    interrupts();
    computeFFTAndMetrics();
  }

  // RS485
  handleRS485();

  // HTTP
  EthernetClient client = server.available();
  if (client) {
    // Esperar datos y atender
    unsigned long t0 = millis();
    while (client.connected() && millis() - t0 < 100) {
      if (client.available()) {
        handleHttpClient(client);
        break;
      }
    }
    delay(1);
    client.stop();
  }
}

Breve explicación de partes clave:
– Selección de CS: csHighAll asegura que solo un dispositivo SPI esté activo a la vez. El W5500 y la SD del shield quedan deseleccionados durante transacciones con el ADXL355.
– adxl355_read20: el ADXL355 entrega 20 bits por eje en 3 bytes; se realiza sign-extend apropiado a 32 bits.
– ODR: se configura en 0x05 para obtener ~1 kHz; si necesitas frecuencias exactas o diferentes, consulta la tabla de ODR/LPF del datasheet (puedes ajustar en ADXL355_REG_FILTER).
– Timer1: genera una IRQ a 1 kHz para muestrear de forma estable sin jitter del loop.
– FFT: se aplica ventana Hann previa al cálculo para reducir leakage; se busca el pico global y se extraen los top N picos.
– RS485: se usa D2 para conmutar el MAX485 entre TX y RX; se adoptan comandos de texto sencillos.
– Ethernet: expone /status con JSON mínimo para supervisión remota.

Bloque 2/2 – Script de validación (Python, PC) para RS485:

# validate_rs485.py
# Requiere: Python 3.10 + pyserial==3.5
# Uso:
#   python3 validate_rs485.py /dev/ttyUSB0 115200
# Conecta el adaptador USB-RS485 al bus A/B junto con el MAX485 del Mega.

import sys
import serial
import time

def send_cmd(ser, cmd):
    ser.write((cmd + "\n").encode("ascii"))
    ser.flush()

def read_line(ser, timeout=2.0):
    ser.timeout = timeout
    line = ser.readline().decode("ascii", errors="ignore").strip()
    return line

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Uso: python3 validate_rs485.py <puerto> <baud>")
        sys.exit(1)

    port = sys.argv[1]
    baud = int(sys.argv[2])

    with serial.Serial(port, baudrate=baud, bytesize=8, parity='N', stopbits=1) as ser:
        time.sleep(0.2)
        send_cmd(ser, "ID?")
        print("-> ID?")
        print("<- " + read_line(ser))

        send_cmd(ser, "GET:RMS")
        print("-> GET:RMS")
        print("<- " + read_line(ser))

        send_cmd(ser, "GET:PEAKS")
        print("-> GET:PEAKS")
        print("<- " + read_line(ser))

        send_cmd(ser, "GET:FFT")
        print("-> GET:FFT (cabecera)")
        print("<- " + read_line(ser))
        print("-> GET:FFT (datos)")
        print("<- " + read_line(ser))

Compilación, carga y ejecución (Arduino CLI)

Comandos exactos y ordenados:

1) Instala Arduino CLI v0.35.3 (si no lo tienes)
– Linux (x86_64):
– wget https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_64bit.tar.gz -O /tmp/arduino-cli.tar.gz
– sudo tar -xzf /tmp/arduino-cli.tar.gz -C /usr/local/bin –strip-components=1 arduino-cli

2) Verifica versión:
– arduino-cli version
– Debe mostrar: arduino-cli Version: 0.35.3

3) Prepara el core AVR:
– arduino-cli core update-index
– arduino-cli core install arduino:avr@1.8.6

4) Prepara un directorio de sketch:
– mkdir -p $HOME/proyectos/fft-vibration-monitor-rs485
– cd $HOME/proyectos/fft-vibration-monitor-rs485
– Crea el archivo fft-vibration-monitor-rs485.ino con el código C++ anterior.

5) Instala librerías exactas:
– arduino-cli lib install «Ethernet@2.0.2»
– arduino-cli lib install «arduinoFFT@1.6.0»

6) Identifica el puerto serie del Mega:
– arduino-cli board list
– Localiza tu Arduino Mega 2560 y anota el puerto (ej.: /dev/ttyACM0)

7) Compila para Arduino Mega 2560 (FQBN: arduino:avr:mega):
– arduino-cli compile –fqbn arduino:avr:mega –warnings all –build-path build .

8) Sube el firmware:
– arduino-cli upload -p /dev/ttyACM0 –fqbn arduino:avr:mega –input-dir build

9) Monitorea logs (opcional, USB a 115200):
– arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200

10) Validación RS485 desde PC (con adaptador USB–RS485):
– pip3 install pyserial==3.5
– python3 validate_rs485.py /dev/ttyUSB0 115200

11) Validación HTTP:
– curl -s http://192.168.1.177/status | jq .
– Si no tienes jq, usa:
– curl -s http://192.168.1.177/status

Validación paso a paso

1) Verificación de IDs del ADXL355:
– Abre el monitor serie por USB (115200).
– Al iniciar, deberías ver:
– “IP: 192.168.1.177”
– “ADXL355 OK”
– “Setup completo.”
– Si aparece “ADXL355 no detectado”, revisa SPI/CS y niveles lógicos.

2) Muestreo y FFT:
– El dispositivo muestrea 256 puntos a 1 kHz (ventana Hann) y calcula FFT.
– No hay UI visual, pero:
– Enviando GET:RMS por RS485, deberías recibir valores ~0.005–0.05 g si el sensor está quieto (ruido térmico + vibración ambiente).
– Enviando GET:PEAKS, con el sensor quieto, el pico dominante puede estar cerca de DC; con un motor pequeño o golpe seco, verás picos a su frecuencia fundamental y armónicos.

3) Prueba RS485:
– Conecta el adaptador USB–RS485 del PC al bus A/B (A→A, B→B).
– Ejecuta el script Python:
– Debes ver:
– “ID,ArduinoMega2560,ADXL355,W5500,MAX485”
– “RMS_G,0.00xxxx”
– “PEAKS, f1,a1, f2,a2, …” (8 picos)
– El comando GET:FFT devuelve cabecera y una línea larga con magnitudes.

4) Prueba HTTP /status:
– curl http://192.168.1.177/status
– Debe retornar un JSON con: device, fs, n, rms_g, peak_freq_hz y top_peaks.
– Repite la petición mientras haces vibrar el sensor; peak_freq_hz se moverá hacia la frecuencia dominante observada.

5) Validación de integridad del bus RS485:
– Si usas línea larga, instala terminación 120 Ω en ambos extremos.
– Verifica que solo exista un par de resistencias de polarización (bias) en todo el bus (en un único punto).

6) Consistencia de SPI:
– Asegúrate de que D10 y D4 estén en HIGH cuando el ADXL355 sea el dispositivo activo, y que D7 esté en HIGH cuando el W5500 sea activo. El sketch ya gestiona esto con csHighAll().

Troubleshooting

1) ADXL355 no responde (IDs incorrectos):
– Síntomas: “ADXL355 no detectado (IDs no coinciden)”.
– Causas probables:
– CS incorrecto: verifica que el ADXL355 esté en D7 y que D10 y D4 estén HIGH durante la transacción.
– Sin nivelador de lógica: si alimentas con 3.3 V y pines de 5 V sin adaptar, el sensor puede dañarse o no responder.
– Cableado SPI incorrecto (MOSI/MISO invertidos).
– Solución: verifica mapeo, nivelador, continuidad y tensiones.

2) Ethernet deja de funcionar al iniciar muestreo:
– Síntomas: /status no responde tras unos segundos.
– Causas: conflicto SPI por CS mal gestionado o ISR muy pesada.
– Solución: confirma csHighAll() antes de operar con ADXL355; reduce la frecuencia SPI si fuera necesario (p. ej., 2 MHz).

3) FFT inconsistente (picos varían mucho):
– Causas: muestreo no estable, ODR no coincide con FS, vibración insuficiente o aliasing.
– Solución:
– Ajusta ADXL355_REG_FILTER para ODR ~ FS (1 kHz).
– Usa DRDY del ADXL355 con attachInterrupt para muestreo exacto por “data ready”.
– Asegura fijación mecánica rígida del sensor (evita foam o cinta blanda).

4) RS485 responde con errores o no responde:
– Causas: sin control de dirección (DE/RE), baudrate distinto, terminación/bias deficientes.
– Solución:
– Verifica que D2 conmute DE/RE (LOW para Rx, HIGH para Tx).
– Asegura 115200 8N1 en ambos lados.
– Añade 120 Ω en extremos y bias en un único punto.

5) Medidas saturadas (g muy altos):
– Causas: rango inadecuado (±2 g) frente a vibraciones fuertes.
– Solución: cambia el rango del ADXL355 (RANGE) a ±4 g o ±8 g (ver datasheet) y actualiza el factor LSB/g.

6) Datos de FFT “planos” (todo ~0):
– Causas: lectura del eje incorrecta (registro mal, bytes mal ensamblados), CS del sensor bajo permanentemente.
– Solución:
– Verifica adxl355_read20: orden de bytes y extensión de signo.
– Comprueba que el pin CS del ADXL355 esté alto en reposo y solo bajo durante la transacción.

7) HTTP bloquea RS485 o viceversa:
– Causas: uso intensivo del loop sin gestionar tiempos; cliente HTTP no libera conexión.
– Solución:
– Mantén timeouts cortos en HTTP (como en el sketch).
– No hagas prints excesivos en Serial.
– Evita operaciones de bloqueo largas dentro del loop.

8) Ruido excesivo en espectro:
– Causas: acoplamiento mecánico pobre, cables largos, interferencias EMI.
– Solución:
– Usa cable apantallado para el sensor si la distancia lo requiere.
– Asegura masa común.
– Filtra en banda (LPF/HPF del ADXL355 vía FILTER) o aplica más promediado.

Mejoras y variantes

  • Sincronización por DRDY:
  • Conecta DRDY del ADXL355 al pin D3 y usa attachInterrupt para leer muestra justo cuando el sensor la tenga lista. Desactiva el Timer1 o úsalo como watchdog. Mejorará la coherencia temporal y la ubicación de picos en frecuencia.

  • Cambiar ventana de FFT:

  • Prueba Blackman, Hamming o Flat Top (si implementas manualmente) para diferentes compromisos entre resolución y amplitud de pico.

  • Publicación UDP/MQTT por Ethernet:

  • Añade un cliente MQTT (p. ej., PubSubClient) o UDP broadcast con las métricas (RMS, pico principal). Esto facilita integración en SCADA/IIoT.

  • Protocolo Modbus RTU por RS485:

  • Estructura registros para RMS, pico, ODR, estado, etc., y usa un stack Modbus RTU esclavo. Esto estandariza la integración.

  • Promediado espectral:

  • Realiza varios bloques de N_SAMPLES, promedia magnitudes (Welch) y reduce varianza. Aumenta estabilidad de picos.

  • Configuración remota:

  • Implementa comandos por RS485/HTTP para cambiar N_SAMPLES, FS, rango del sensor, IP estática y número de picos a reportar.

  • Ejes múltiples:

  • Procesa X/Y/Z y reporta vector RMS y picos por eje. Aumenta el costo computacional; considera N=128 por eje para mantener tiempos.

Checklist de verificación

  • [ ] Toolchain exacta instalada:
  • [ ] Arduino CLI v0.35.3
  • [ ] Core arduino:avr@1.8.6
  • [ ] Librerías: Ethernet@2.0.2, arduinoFFT@1.6.0

  • [ ] Cableado correcto y coherente:

  • [ ] W5500 en Shield con CS D10 y SD CS D4 (alto si no se usa).
  • [ ] ADXL355 a 3.3 V, SPI con nivelador y CS en D7.
  • [ ] DRDY del ADXL355 a D3 (opcional).
  • [ ] MAX485: RO→D19 (RX1), DI→D18 (TX1), DE/RE→D2, A/B al bus.

  • [ ] RS485 preparado:

  • [ ] Terminación 120 Ω en los extremos del bus.
  • [ ] Bias en un único punto (si el bus lo requiere).
  • [ ] Adaptador USB–RS485 en el PC y polaridad A/B correcta.

  • [ ] Compilación y carga:

  • [ ] arduino-cli core update-index / install realizados.
  • [ ] arduino-cli lib install con versiones exactas.
  • [ ] Compilado con FQBN arduino:avr:mega y subido sin errores.

  • [ ] Arranque correcto:

  • [ ] Monitor USB muestra IP y “ADXL355 OK”.
  • [ ] /status responde por HTTP.

  • [ ] Validación funcional:

  • [ ] GET:RMS devuelve un valor coherente (quieto vs. vibrando).
  • [ ] GET:PEAKS muestra frecuencias lógicas cuando se activa un vibrador/motor.
  • [ ] GET:FFT devuelve cabecera y datos.

  • [ ] Estabilidad espectral:

  • [ ] Picos consistentes al repetir medición.
  • [ ] Sin bloqueos al alternar RS485/HTTP.

Con este caso práctico has construido un monitor de vibraciones FFT robusto sobre RS485 utilizando exactamente el combo “Arduino Mega 2560 + Ethernet Shield W5500 + ADXL355 + MAX485”, compilado y desplegado con Arduino CLI (core arduino:avr@1.8.6), y validado tanto por RS485 como por HTTP. Esta base es extensible hacia protocolos industriales (Modbus RTU/TCP) y a técnicas de análisis espectral más avanzadas.

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: ¿Cuál es el sistema operativo base recomendado para el proyecto?




Pregunta 2: ¿Qué versión de Arduino CLI se debe utilizar?




Pregunta 3: ¿Qué librería se utiliza para la comunicación Ethernet en este proyecto?




Pregunta 4: ¿Cuál es el modelo del acelerómetro utilizado?




Pregunta 5: ¿Qué tipo de transceptor RS485 se menciona en los requisitos?




Pregunta 6: ¿Qué comando se debe usar para añadir un usuario al grupo 'dialout'?




Pregunta 7: ¿Qué tipo de conexión se requiere para el ADXL355?




Pregunta 8: ¿Cuál es la resistencia de terminación recomendada para RS485?




Pregunta 9: ¿Qué voltaje de alimentación se requiere para el ADXL355?




Pregunta 10: ¿Cuál es el directorio de trabajo recomendado para el proyecto?




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