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




