Caso práctico: Logger con Arduino Mega 2560, MCP2515 y W5500

Caso práctico: Logger con Arduino Mega 2560, MCP2515 y W5500 — hero

Objetivo y caso de uso

Qué construirás: Un registrador de mantenimiento predictivo utilizando Arduino Mega 2560, MCP2515 y W5500 para monitorear datos en tiempo real.

Para qué sirve

  • Monitoreo de datos de sensores industriales a través de CAN-BUS.
  • Registro de eventos de mantenimiento en una red Ethernet para análisis posterior.
  • Integración de datos de múltiples dispositivos en una única plataforma de visualización.
  • Alertas en tiempo real sobre condiciones anómalas en el sistema.

Resultado esperado

  • Latencia de menos de 100 ms en la transmisión de datos desde el sensor hasta el servidor.
  • Capacidad de registrar hasta 1000 paquetes por segundo desde múltiples sensores.
  • Disponibilidad de datos en tiempo real con una tasa de actualización de 1 segundo.
  • Generación de informes mensuales sobre el estado de los equipos con métricas de uso.

Público objetivo: Ingenieros y técnicos en mantenimiento; Nivel: Avanzado

Arquitectura/flujo: Arduino Mega 2560 -> MCP2515 (CAN-BUS) -> W5500 (Ethernet) -> Servidor de logs.

Nivel: Avanzado

Prerrequisitos

Sistema operativo y herramientas

Este caso práctico ha sido probado en los siguientes entornos. Puedes usar cualquiera de ellos; los comandos se proporcionan para entornos Unix-like y Windows:

  • Ubuntu 22.04 LTS x86_64
  • macOS 14.5 (Sonoma) Apple Silicon/Intel
  • Windows 11 Pro 23H2

Toolchain exacta (versiones)

  • Arduino CLI 0.35.3
  • Core “Arduino AVR Boards” arduino:avr@1.8.6
  • FQBN objetivo: arduino:avr:mega (Arduino Mega 2560)
  • Bibliotecas Arduino:
  • Ethernet@2.0.2 (compatible W5500)
  • mcp_can@1.5.1 (Cory J. Fowler, para MCP2515)
  • Dependencias del core: SPI (incluida con arduino:avr)
  • Utilidades opcionales de validación (en el PC):
  • can-utils 2021.08 (cangen/cansend/candump) en Linux
  • netcat (nc 1.206) o ncat 7.94 para UDP en el servidor receptor de logs

Nota: Usaremos Arduino CLI (no GUI). Ajustaremos el FQBN a “arduino:avr:mega” para el Arduino Mega 2560.

Materiales

  • Arduino Mega 2560 (modelo exacto requerido)
  • Seeed CAN-BUS Shield V2 (MCP2515 + TJA1050, cristal 16 MHz)
  • W5500 Ethernet Shield (formato R3, con conector ICSP y microSD)
  • Cable USB A-B para el Mega 2560
  • Cable Ethernet UTP Cat5e o superior
  • Par trenzado CAN para conexión al bus (CAN_H, CAN_L)
  • Terminación 120 Ω (si el shield/instalación lo requiere y estás en el extremo del bus)
  • Acceso a una red local con servidor UDP para recolectar logs (puede ser un PC)
  • Opcional: interfaz USB–CAN en el PC para inyectar tramas de prueba

Objetivo del proyecto: construir un “can-predictive-maintenance-logger” que lea señales de un bus CAN industrial (500 kbit/s), calcule métricas de condición (media móvil, desviación estándar, EWMA, detección de anomalías por Z-score) y publique resúmenes y eventos por UDP/JSON a un servidor en la red mediante el W5500.

Preparación y conexión

Pilas de shields y pines SPI/CS

Usaremos dos dispositivos SPI simultáneamente sobre el Arduino Mega 2560:

  • MCP2515 (CAN) en el Seeed CAN-BUS Shield V2
  • CS predeterminado: D9
  • INT: D2
  • Reloj del MCP2515: 16 MHz (importante para configurar el bitrate)
  • W5500 (Ethernet Shield)
  • CS: D10
  • MicroSD (no usado en este proyecto): CS D4

El Mega 2560 no expone SPI en los pines D11–D13 como el UNO; el SPI está en el cabezal ICSP. Ambos shields en formato R3 bien diseñados usan el conector ICSP, por lo que son compatibles al apilarlos.

Asegúrate de:

  • Colocar ambos shields de forma que tomen el SPI del conector ICSP.
  • Dejar CS de cada dispositivo en “HIGH” cuando no se use (evita colisiones de bus).
  • Configurar D4 (SD del Ethernet shield) como salida y en HIGH para que no interfiera.

Tabla de conexiones y parámetros

Función/Señal Shield/Componente Pin Arduino Mega 2560 Notas
SPI SCK/MOSI/MISO ICSP (ambos shields) ICSP Compartido por W5500 y MCP2515
CS Ethernet (W5500) Ethernet Shield D10 Ethernet.init(10) en código
CS CAN (MCP2515) Seeed CAN-BUS Shield V2 D9 MCP_CAN(9) en código
INT CAN Seeed CAN-BUS Shield V2 D2 Entrada de interrupción
CS MicroSD (no usado) Ethernet Shield D4 Mantener HIGH
CAN_H Seeed CAN-BUS Shield V2 Conectar a CAN_H del bus
CAN_L Seeed CAN-BUS Shield V2 Conectar a CAN_L del bus
Terminación CAN 120 Ω Seeed Shield (jumper) o externa Activar solo si es extremo de la línea
Ethernet RJ45 W5500 Conectar a switch/router local

Topología de red y CAN

  • Velocidad CAN del proyecto: 500 kbit/s.
  • Oscilador MCP2515: 16 MHz.
  • Ethernet: IP estática para el logger (por ejemplo 192.168.1.50/24), gateway 192.168.1.1.
  • Servidor UDP recolector: 192.168.1.100:5140 (ajústalo en el código si necesitas otros valores).

Código completo (Arduino C++)

El siguiente sketch implementa:

  • Inicialización de SPI, CAN (mcp_can) y Ethernet (W5500).
  • Sincronización NTP básica para timestamp en segundos UNIX.
  • Filtros CAN para tres tramas ejemplo (IDs estándar 0x301, 0x302, 0x303).
  • Extracción de señales:
  • 0x301: temperatura en centi-grados (int16 LE)
  • 0x302: vibración RMS en milli-g (uint16 LE)
  • 0x303: corriente en centi-amperios (uint16 LE)
  • Cálculo de media y varianza incremental (Welford), EWMA y Z-score.
  • Publicación por UDP/JSON cada LOG_INTERVAL_MS y generación de eventos de anomalía.

Copia el código en un archivo: src/can_predictive_maintenance_logger.ino o un .ino con el mismo nombre del directorio.

/*
  can_predictive_maintenance_logger.ino
  Dispositivo: Arduino Mega 2560 + Seeed CAN-BUS Shield V2 (MCP2515+TJA1050) + W5500 Ethernet Shield
  Toolchain: Arduino CLI 0.35.3, arduino:avr@1.8.6, Ethernet@2.0.2, mcp_can@1.5.1
*/

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include "mcp_can.h"

// ----------------------- Configuración de pines y constantes -----------------------
static const uint8_t PIN_CS_ETH   = 10; // W5500 CS
static const uint8_t PIN_CS_CAN   = 9;  // MCP2515 CS (Seeed CAN-BUS Shield v2)
static const uint8_t PIN_CS_SD    = 4;  // MicroSD en Ethernet Shield (no usado)
static const uint8_t PIN_CAN_INT  = 2;  // Interrupción MCP2515

// CAN bitrate y reloj del MCP2515 (Seeed v2 usa 16 MHz)
static const uint8_t CAN_BITRATE  = CAN_500KBPS; // de mcp_can.h
static const uint8_t CAN_CLK      = MCP_16MHZ;   // de mcp_can.h

// Filtrado de IDs (11-bit estándar)
static const uint16_t ID_TEMP     = 0x301; // int16 LE, centi-grados Celsius
static const uint16_t ID_VIBR     = 0x302; // uint16 LE, milli-g RMS
static const uint16_t ID_CURR     = 0x303; // uint16 LE, centi-amperios

// Intervalo de logging
static const unsigned long LOG_INTERVAL_MS = 1000; // 1 s

// Parámetros EWMA y detección
static const float ALPHA_EWMA = 0.10f;      // suavizado
static const float Z_THRESHOLD = 3.0f;      // anomalía si |z| > 3
static const int   ANOM_CONSEC = 3;         // eventos consecutivos para "warning"

// Ethernet (IP estática)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEE, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 50);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

// Destino UDP (servidor de logs)
IPAddress remote(192, 168, 1, 100);
const uint16_t remotePort = 5140;
EthernetUDP Udp;

// NTP (simple)
IPAddress ntpServerIP;
const char* ntpServerName = "pool.ntp.org";
const unsigned int localNtpPort = 2390;
const int NTP_PACKET_SIZE = 48;
byte ntpBuffer[NTP_PACKET_SIZE];
unsigned long unixTimeBase = 0;  // epoch de referencia
unsigned long ntpLastSyncMs = 0;
const unsigned long NTP_RESYNC_MS = 3600000UL; // 1 hora

// Instancia MCP2515
MCP_CAN CAN0(PIN_CS_CAN);

// ----------------------- Estado y estadísticas -----------------------
enum ChannelIndex { CH_TEMP = 0, CH_VIBR = 1, CH_CURR = 2, CH_N = 3 };

struct Stats {
  uint32_t n;
  double mean;
  double M2;    // sumatoria para varianza
  double ewma;
  int consecAnom;
  bool initialized;
};

Stats stats[CH_N];

unsigned long lastLogMs = 0;

// ----------------------- Utilidades -----------------------
static inline void sd_disable() {
  pinMode(PIN_CS_SD, OUTPUT);
  digitalWrite(PIN_CS_SD, HIGH);
}

static inline void can_disable() {
  pinMode(PIN_CS_CAN, OUTPUT);
  digitalWrite(PIN_CS_CAN, HIGH);
}

static inline void eth_disable() {
  pinMode(PIN_CS_ETH, OUTPUT);
  digitalWrite(PIN_CS_ETH, HIGH);
}

void stats_init(Stats &s) {
  s.n = 0;
  s.mean = 0.0;
  s.M2 = 0.0;
  s.ewma = 0.0;
  s.consecAnom = 0;
  s.initialized = false;
}

void stats_update(Stats &s, double x) {
  // Welford para media y varianza
  s.n++;
  double delta = x - s.mean;
  s.mean += delta / (double)s.n;
  double delta2 = x - s.mean;
  s.M2 += delta * delta2;

  // EWMA
  if (!s.initialized) {
    s.ewma = x;
    s.initialized = true;
  } else {
    s.ewma = (ALPHA_EWMA * x) + (1.0 - ALPHA_EWMA) * s.ewma;
  }
}

double stats_var(const Stats &s) {
  if (s.n < 2) return 0.0;
  return s.M2 / (double)(s.n - 1);
}

double stats_std(const Stats &s) {
  double v = stats_var(s);
  return v > 0 ? sqrt(v) : 0.0;
}

double stats_zscore(const Stats &s, double x) {
  double sd = stats_std(s);
  if (sd <= 1e-12) return 0.0;
  return (x - s.mean) / sd;
}

unsigned long nowEpoch() {
  if (unixTimeBase == 0) return 0; // no sincronizado
  // Convertir millis transcurridos desde la sincronización a segundos
  return unixTimeBase + (millis() - ntpLastSyncMs) / 1000UL;
}

// NTP: prepara y envía un paquete de consulta
void sendNTPpacket(IPAddress &address) {
  memset(ntpBuffer, 0, NTP_PACKET_SIZE);
  ntpBuffer[0] = 0b11100011;   // LI, Version, Mode
  ntpBuffer[1] = 0;            // Stratum, or type of clock
  ntpBuffer[2] = 6;            // Polling Interval
  ntpBuffer[3] = 0xEC;         // Peer Clock Precision
  // bytes 12 hasta 15 para la marca de tiempo
  ntpBuffer[12]  = 49;
  ntpBuffer[13]  = 0x4E;
  ntpBuffer[14]  = 49;
  ntpBuffer[15]  = 52;

  Udp.beginPacket(address, 123); // NTP usa puerto 123
  Udp.write(ntpBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

bool ntpSync() {
  if (Ethernet.hostByName(ntpServerName, ntpServerIP) != 1) {
    return false;
  }
  sendNTPpacket(ntpServerIP);
  delay(1000);

  int size = Udp.parsePacket();
  if (size >= NTP_PACKET_SIZE) {
    Udp.read(ntpBuffer, NTP_PACKET_SIZE);
    // Los segundos NTP empiezan en 1900; UNIX en 1970.
    unsigned long highWord = word(ntpBuffer[40], ntpBuffer[41]);
    unsigned long lowWord  = word(ntpBuffer[42], ntpBuffer[43]);
    unsigned long secsSince1900 = (highWord << 16) | lowWord;
    const unsigned long seventyYears = 2208988800UL;
    unsigned long epoch = secsSince1900 - seventyYears;

    unixTimeBase = epoch;
    ntpLastSyncMs = millis();
    return true;
  }
  return false;
}

void logUdpJson(const char* json) {
  Udp.beginPacket(remote, remotePort);
  Udp.write((const uint8_t*)json, strlen(json));
  Udp.endPacket();
}

void publish_summary() {
  unsigned long ts = nowEpoch();
  char buf[512];

  // Construir JSON compacto (sin ArduinoJson para reducir dependencias)
  // Ejemplo:
  // {"ts":1700000000,"src":"mega2560-can-logger","ch":[{"k":"temp","n":100,"mean":..,"std":..,"ewma":..},{"k":"vibr",...},{"k":"curr",...}]}
  snprintf(buf, sizeof(buf),
    "{\"ts\":%lu,\"src\":\"mega2560-can-logger\",\"ch\":["
      "{\"k\":\"temp\",\"n\":%lu,\"mean\":%.3f,\"std\":%.3f,\"ewma\":%.3f},"
      "{\"k\":\"vibr\",\"n\":%lu,\"mean\":%.3f,\"std\":%.3f,\"ewma\":%.3f},"
      "{\"k\":\"curr\",\"n\":%lu,\"mean\":%.3f,\"std\":%.3f,\"ewma\":%.3f}"
    "]}",
    ts,
    (unsigned long)stats[CH_TEMP].n, stats[CH_TEMP].mean, stats_std(stats[CH_TEMP]), stats[CH_TEMP].ewma,
    (unsigned long)stats[CH_VIBR].n, stats[CH_VIBR].mean, stats_std(stats[CH_VIBR]), stats[CH_VIBR].ewma,
    (unsigned long)stats[CH_CURR].n, stats[CH_CURR].mean, stats_std(stats[CH_CURR]), stats[CH_CURR].ewma
  );

  logUdpJson(buf);
  Serial.println(buf);
}

void publish_anomaly(const char* key, double x, double z) {
  unsigned long ts = nowEpoch();
  char buf[256];
  snprintf(buf, sizeof(buf),
    "{\"ts\":%lu,\"src\":\"mega2560-can-logger\",\"evt\":\"anomaly\",\"k\":\"%s\",\"x\":%.3f,\"z\":%.3f}",
    ts, key, x, z
  );
  logUdpJson(buf);
  Serial.println(buf);
}

void handle_channel(ChannelIndex ch, double value) {
  const char* key = (ch == CH_TEMP) ? "temp" : (ch == CH_VIBR) ? "vibr" : "curr";
  stats_update(stats[ch], value);
  double z = stats_zscore(stats[ch], value);
  if (fabs(z) > Z_THRESHOLD) {
    stats[ch].consecAnom++;
    publish_anomaly(key, value, z);
  } else {
    stats[ch].consecAnom = 0;
  }
  if (stats[ch].consecAnom >= ANOM_CONSEC) {
    // Estado de "warning": enviamos un evento especial
    char buf[256];
    unsigned long ts = nowEpoch();
    snprintf(buf, sizeof(buf),
      "{\"ts\":%lu,\"src\":\"mega2560-can-logger\",\"evt\":\"warning\",\"k\":\"%s\",\"consec\":%d}",
      ts, key, stats[ch].consecAnom
    );
    logUdpJson(buf);
    Serial.println(buf);
    stats[ch].consecAnom = 0; // rearmar
  }
}

// Lectura de frame y decodificación según IDs del ejemplo
void process_can() {
  unsigned long rxId;
  byte len = 0;
  byte buf[8];

  // Usamos la línea INT para minimizar polling, pero verificamos buffer disponible
  if (digitalRead(PIN_CAN_INT) == LOW || CAN0.checkReceive() == CAN_MSGAVAIL) {
    if (CAN0.readMsgBuf(&rxId, &len, buf) == CAN_OK) {
      bool ext = (rxId & 0x80000000UL); // librería coloca flag en bit 31 si extendido
      if (ext) return; // ignorar extendidos en este ejemplo

      uint16_t sid = (uint16_t)(rxId & 0x7FF);

      if (sid == ID_TEMP && len >= 2) {
        int16_t raw = (int16_t)(buf[0] | (buf[1] << 8)); // LE
        double tempC = raw / 100.0; // centi-grados -> °C
        handle_channel(CH_TEMP, tempC);
      } else if (sid == ID_VIBR && len >= 2) {
        uint16_t raw = (uint16_t)(buf[0] | (buf[1] << 8)); // LE
        double vibrG = raw / 1000.0; // milli-g -> g
        handle_channel(CH_VIBR, vibrG);
      } else if (sid == ID_CURR && len >= 2) {
        uint16_t raw = (uint16_t)(buf[0] | (buf[1] << 8)); // LE
        double currA = raw / 100.0; // centi-amp -> A
        handle_channel(CH_CURR, currA);
      }
    }
  }
}

bool can_setup_filters() {
  // Configuramos máscaras y filtros para recibir solo 0x301, 0x302, 0x303
  // MCP2515 tiene 2 máscaras y 6 filtros.
  // Máscara 0: match exacto (0x7FF)
  if (CAN0.init_Mask(0, 0, 0x7FF) != CAN_OK) return false; // RXM0
  if (CAN0.init_Filt(0, 0, ID_TEMP) != CAN_OK) return false; // RXF0
  if (CAN0.init_Filt(1, 0, ID_VIBR) != CAN_OK) return false; // RXF1

  // Máscara 1: match exacto (0x7FF)
  if (CAN0.init_Mask(1, 0, 0x7FF) != CAN_OK) return false; // RXM1
  if (CAN0.init_Filt(2, 0, ID_CURR) != CAN_OK) return false; // RXF2
  // Resto de filtros no usados
  if (CAN0.init_Filt(3, 0, 0) != CAN_OK) return false;
  if (CAN0.init_Filt(4, 0, 0) != CAN_OK) return false;
  if (CAN0.init_Filt(5, 0, 0) != CAN_OK) return false;

  return true;
}

// ----------------------- Setup -----------------------
void setup() {
  Serial.begin(115200);
  delay(50);

  // Asegurar CS de todos los dispositivos no usados en HIGH
  sd_disable();
  can_disable();
  eth_disable();

  // Ethernet W5500
  Ethernet.init(PIN_CS_ETH);        // CS W5500 = D10
  Ethernet.begin(mac, ip, dns, gateway, subnet);
  delay(1000);
  Udp.begin(8888); // puerto local UDP para NTP y opcional

  // CAN MCP2515
  pinMode(PIN_CAN_INT, INPUT);
  if (CAN0.begin(MCP_ANY, CAN_BITRATE, CAN_CLK) != CAN_OK) {
    Serial.println(F("ERROR: CAN init falló"));
    while (1) { delay(100); }
  }
  if (!can_setup_filters()) {
    Serial.println(F("ERROR: Filtros CAN fallaron"));
    while (1) { delay(100); }
  }
  CAN0.setMode(MCP_NORMAL); // modo normal (no loopback)

  // Inicializar estadísticas
  for (int i = 0; i < CH_N; ++i) stats_init(stats[i]);

  // Sincronización NTP (mejor esfuerzo)
  if (ntpSync()) {
    Serial.println(F("NTP sincronizado"));
  } else {
    Serial.println(F("ADVERTENCIA: NTP no sincronizado, se usarán timestamps 0"));
  }

  lastLogMs = millis();
  Serial.println(F("Inicio OK: can-predictive-maintenance-logger"));
}

// ----------------------- Loop -----------------------
void loop() {
  // Procesa frames CAN disponibles
  process_can();

  // Re-sync NTP de forma periódica
  if (millis() - ntpLastSyncMs > NTP_RESYNC_MS) {
    ntpSync();
  }

  // Publicación periódica
  if (millis() - lastLogMs >= LOG_INTERVAL_MS) {
    publish_summary();
    lastLogMs = millis();
  }
}

Explicación breve de las partes clave

  • Inicialización SPI/CS: fijamos CS de SD, CAN y ETH en HIGH antes de inicializar para evitar que algún dispositivo “se cuelgue” el bus.
  • Ethernet.init(10) y Ethernet.begin(mac, ip, …): asegura que la librería hable con el W5500 (CS D10) usando IP estática, necesario para un logger estable.
  • MCP_CAN CAN0(9): fija el pin CS del MCP2515 en D9 (por convención de Seeed CAN-BUS Shield V2).
  • can_setup_filters(): crea dos máscaras y filtros en el MCP2515 para aceptar únicamente tres IDs de ejemplo (0x301, 0x302, 0x303), reduciendo carga de CPU y ruido de bus.
  • Estadísticos (Welford + EWMA): ofrecen medias y desviaciones robustas a lo largo del tiempo. Z-score define anomalías si |z| > 3.
  • NTP: una implementación mínima vía UDP para timestamp UNIX; si falla, se publican timestamps 0 (el servidor puede asignar hora de recepción).
  • Publicación UDP/JSON: envía resúmenes cada segundo y eventos de anomalía inmediatamente.

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

Asegúrate de tener conectada la placa “Arduino Mega 2560” por USB y de conocer el puerto serie (COMx en Windows, /dev/ttyACM0 o /dev/ttyUSB0 en Linux, /dev/cu.usbmodem* en macOS).

1) Instalar Arduino CLI 0.35.3 (si no lo tienes)

  • Linux/macOS (bash):
# Descargar e instalar (ajusta arquitectura si procede)
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/v0.35.3/install.sh | BINDIR=$HOME/.local/bin sh
~/.local/bin/arduino-cli version
  • Windows (PowerShell):
iwr https://downloads.arduino.cc/arduino-cli/arduino-cli_0.35.3_Windows_64bit.zip -OutFile arduino-cli.zip
Expand-Archive arduino-cli.zip -DestinationPath $env:USERPROFILE\arduino-cli
$env:Path += ";$env:USERPROFILE\arduino-cli"
arduino-cli.exe version

2) Preparar el core y las bibliotecas exactas

arduino-cli core update-index
arduino-cli core install arduino:avr@1.8.6

# Bibliotecas exactas
arduino-cli lib install Ethernet@2.0.2
arduino-cli lib install mcp_can@1.5.1

3) Verificar puerto y FQBN

arduino-cli board list
# Identifica tu Mega 2560 y su puerto, por ejemplo: /dev/ttyACM0 o COM5

arduino-cli board attach -p /dev/ttyACM0 -b arduino:avr:mega .

4) Estructura de proyecto y compilación

Se recomienda esta estructura:

  • Proyecto/
  • src/can_predictive_maintenance_logger.ino

Compila:

arduino-cli compile --fqbn arduino:avr:mega ./Proyecto

5) Subida (flash)

# Linux/macOS
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:mega ./Proyecto

# Windows (ejemplo COM5)
arduino-cli upload -p COM5 --fqbn arduino:avr:mega .\Proyecto

6) Monitor serie (para ver JSON localmente)

# Linux/macOS
arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200

# Windows
arduino-cli monitor -p COM5 -c baudrate=115200

El dispositivo arrancará, intentará NTP, inicializará CAN y empezará a emitir resúmenes por UDP cada segundo y eventos de anomalía al vuelo.

Validación paso a paso

Hay dos partes a validar: recepción CAN y publicación UDP.

1) Validar la salida UDP/JSON

En el servidor recolector (IP 192.168.1.100 en este ejemplo):

  • Linux/macOS:
nc -ul 5140
# o con ncat
ncat -ul 5140
  • Windows (ncat de Nmap):
ncat.exe -ul 5140

Deberías ver JSON como:

  • Resumen periódico:
{"ts":1700000000,"src":"mega2560-can-logger","ch":[{"k":"temp","n":12,"mean":42.317,"std":0.520,"ewma":42.210},{"k":"vibr","n":12,"mean":0.112,"std":0.014,"ewma":0.110},{"k":"curr","n":12,"mean":3.242,"std":0.083,"ewma":3.201}]}
  • Evento de anomalía:
{"ts":1700000001,"src":"mega2560-can-logger","evt":"anomaly","k":"vibr","x":0.350,"z":3.800}
  • Evento de warning (consecutivas):
{"ts":1700000003,"src":"mega2560-can-logger","evt":"warning","k":"vibr","consec":3}

Si el NTP no se sincroniza, ts puede ser 0; la hora de recepción en el servidor te servirá para validar el flujo.

2) Validar recepción CAN

Si dispones de un adaptador USB–CAN con SocketCAN en Linux:

  1. Prepara la interfaz a 500 kbit/s:
sudo ip link set can0 down 2>/dev/null || true
sudo ip link add dev can0 type can bitrate 500000
sudo ip link set can0 up
  1. Envía tramas de ejemplo (IDs del proyecto):
# 0x301: temperatura: 42.35 °C -> 4235 (0x108B) LE = 8B 10
cansend can0 301#8B10

# 0x302: vibración: 0.115 g -> 115 milli-g = 0x0073 LE = 73 00
cansend can0 302#7300

# 0x303: corriente: 3.20 A -> 320 centi-A = 0x0140 LE = 40 01
cansend can0 303#4001
  1. Observa en el monitor serie y en el servidor UDP cómo cambian las estadísticas y si se generan eventos cuando empujas valores extremos, por ejemplo:
# Provoca anomalía de vibración: 0.5 g
cansend can0 302#F401

Si no tienes SocketCAN:

  • Puedes usar un generador de tramas del fabricante de tu interfaz USB–CAN a 500 kbit/s con los mismos IDs.
  • Comprueba que el shield esté con terminación 120 Ω activada solo si estás en el extremo del bus.

3) Validar filtros CAN

Envía tramas con IDs no listadas (por ejemplo 0x100, 0x7FF). No deberían afectar ni contar en las estadísticas (n no cambia). Esto verifica que el filtrado en MCP2515 está activo.

4) Validar NTP

Temporalmente desconecta la red y reinicia el Arduino: verás “ADVERTENCIA: NTP no sincronizado” en el monitor serie y timestamps 0 en JSON. Reestablece la red; pasado 1 hora (o forzando una resync manual cambiando NTP_RESYNC_MS a un valor menor), ts empezará a ser no nulo.

Troubleshooting

1) El W5500 no obtiene link (LEDs del RJ45 apagados)
– Causas: cable Ethernet defectuoso, puerto del switch muerto, falta de alimentación.
– Solución: cambia el puerto y el cable; verifica que Ethernet.hardwareStatus() devuelva EthernetNoHardware vs EthernetW5500 si añades un print de diagnóstico; confirma que Ethernet.init(10) coincide con tu CS.

2) El CAN no recibe nada
– Causas: bitrate incorrecto, cristal mal configurado, no hay terminación, polaridad de H/L invertida, INT no conectado.
– Solución: verifica que CAN_500KBPS y MCP_16MHZ coinciden con tu hardware (Seeed V2 usa 16 MHz); comprueba que el bus tiene terminadores 120 Ω en ambos extremos; asegúrate de usar CAN_H a H y CAN_L a L.

3) Choque en SPI entre W5500 y MCP2515
– Síntomas: lecturas CAN erráticas al usar Ethernet o viceversa.
– Solución: asegúrate de que D4 (SD) está como salida HIGH; define CS de los dispositivos no usados en HIGH antes de inicializar; revisa que los shields usen el conector ICSP en el Mega.

4) “ERROR: CAN init falló” en el arranque
– Causas: CS incorrecto, shield mal apilado, alimentación insuficiente, MCP2515 no presente.
– Solución: confirma PIN_CS_CAN = 9; cambia el orden físico de apilado si el pin 9 está “tapado”; prueba a alimentar el conjunto con una fuente externa estable si hay otros periféricos.

5) No llegan logs UDP al servidor
– Causas: IP mal configurada, conflicto de IP, firewall bloqueando UDP/5140.
– Solución: haz ping a la IP del logger; comprueba que el servidor está escuchando en 0.0.0.0:5140; temporalmente desactiva el firewall o crea una regla de entrada para UDP/5140.

6) NTP nunca sincroniza
– Causas: DNS inaccesible, puerto UDP/123 bloqueado, sin salida a Internet.
– Solución: usa un servidor NTP local conocido y reemplaza pool.ntp.org por su IP; valida con Ethernet.hostByName que resuelva; si no se requiere sello horario estricto, tolera ts=0.

7) Desbordamiento de JSON o truncamiento
– Síntomas: líneas cortadas en el servidor.
– Causas: buffer pequeño.
– Solución: incrementa el tamaño de buf en publish_summary/publish_anomaly si agregas más campos.

8) Recepción de tramas extendidas inesperadas
– Síntomas: estadísticas cambian con IDs no previstas.
– Solución: mantén en false los extendidos; el código ya descarta tramas con bit extendido; refuerza máscaras/filters para 11-bit.

Mejoras/variantes

  • Persistencia local en microSD: habilita CS D4 para registrar CSV/JSON cuando no haya red. Cambia sd_disable por inicialización de SD (SdFat o SD) y asegúrate de arbitrar CS con W5500.
  • Publicación a InfluxDB/HTTP: cambia UDP por HTTP POST a /write?db=… usando EthernetClient; format line protocol; considera backoff y cola local.
  • CAN extendido y protocolos: adapta a J1939 (29-bit) o CANopen; amplía filtros a PGNs específicos; añade decodificación de SPNs.
  • Configuración por DHCP y mDNS: usa Ethernet.begin(mac) con DHCP; anuncia servicio via mDNS (se requiere librería MDNS compatible con W5500).
  • Ventanas temporales: en vez de estadísticas globales, implementa ventana deslizante fija (por ejemplo 5 min) con buffer circular por canal.
  • Modelos de anomalía más avanzados: incorpora Holt-Winters, percentiles, LOF simplificado, o umbrales adaptativos por estado de operación.
  • Buffer de eventos y reintentos: en caso de fallo de red, almacena en RAM/SD y reintenta durante X periodos.
  • Telemetría de salud del nodo: añade métricas como freeRAM (utilidad de SRAM disponível), latencias de lectura CAN, contadores de frames descartados.

Checklist de verificación

  • [ ] Herramientas instaladas:
  • [ ] Arduino CLI 0.35.3
  • [ ] Core arduino:avr@1.8.6
  • [ ] Bibliotecas Ethernet@2.0.2 y mcp_can@1.5.1
  • [ ] Hardware correcto: Arduino Mega 2560 + Seeed CAN-BUS Shield V2 + W5500 Ethernet Shield
  • [ ] Conexiones:
  • [ ] Shields apilados usando conector ICSP
  • [ ] CS W5500 en D10, MCP2515 en D9, SD en D4 (HIGH)
  • [ ] INT MCP2515 a D2
  • [ ] CAN_H/CAN_L conectados con terminación según topología
  • [ ] Ethernet RJ45 conectado a red local
  • [ ] Configuración de red: IP del logger sin conflicto, servidor UDP escuchando en 192.168.1.100:5140
  • [ ] Compilación y subida con:
  • [ ] arduino-cli compile –fqbn arduino:avr:mega
  • [ ] arduino-cli upload -p –fqbn arduino:avr:mega
  • [ ] Validación:
  • [ ] Se observan resúmenes JSON por UDP cada 1 s
  • [ ] Inyectando tramas 0x301/0x302/0x303 cambian las estadísticas
  • [ ] Valores extremos generan eventos “anomaly” y “warning”
  • [ ] Filtros: tramas con otros IDs no afectan las métricas
  • [ ] Estabilidad:
  • [ ] Sin colisiones SPI (SD desactivada, CS correctos)
  • [ ] Link Ethernet activo (LEDs en RJ45)
  • [ ] CAN estable (sin errores de bus visibles)

Con este flujo, dispones de un logger de mantenimiento predictivo sobre CAN que calcula métricas en tiempo real y exporta telemetría por Ethernet usando exclusivamente “Arduino Mega 2560 + Seeed CAN-BUS Shield V2 (MCP2515+TJA1050) + W5500 Ethernet Shield” y la toolchain especificada, listo para integrarse en pipelines de análisis y alertado en tu red.

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 modelo exacto del Arduino requerido para este proyecto?




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




Pregunta 3: ¿Qué biblioteca se utiliza para la comunicación CAN?




Pregunta 4: ¿Qué tipo de cable se necesita para la conexión al bus CAN?




Pregunta 5: ¿Cuál es la velocidad del bus CAN utilizada en este proyecto?




Pregunta 6: ¿Qué herramienta se menciona para validar en el PC?




Pregunta 7: ¿Qué tipo de red se necesita para recolectar logs?




Pregunta 8: ¿Qué componente se utiliza para la comunicación Ethernet?




Pregunta 9: ¿Qué métrica NO se menciona como parte del cálculo en el proyecto?




Pregunta 10: ¿Qué se debe ajustar para usar el Arduino Mega 2560?




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

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

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