Implementación de TRDP en STM32 para Redes Ethernet Ferroviarias en Tiempo Real

Diseño de la Arquitectura del Sistema

Configuración de la Plataforma de Hardware

Microcontrolador principal: STM32F407ZGT6 (168MHz, 1MB Flash, 192KB RAM)
Controlador de red: LAN8742A (interfaz RMII, 10/100Mbps)
Memoria: SDRAM externa de 8MB (utilizada como caché para la pila de protocolo)
Almacenamiento: SPI Flash de 16MB (para configuraciones persistentes)
Reloj de tiempo real: DS3231 (conexión mediante I2C)

Capas de la Pila de Protocolo TRDP

Capa de aplicación (lógica de control del tren)

Capa de protocolo TRDP (gestión de PD/MD)

Capa UDP/IP

Capa MAC Ethernet (integrada en el STM32)

Capa PHY (LAN8742A)

Código Fuente Completo del Proyecto

Archivo de Cabecera de Definiciones del Protocolo TRDP (definiciones_trdp.h)

#ifndef DEFINICIONES_TRDP_H
#define DEFINICIONES_TRDP_H

#include <stdint.h>
#include <stdbool.h>

// Constantes de configuración TRDP
#define VERSION_PROTO_TRDP      0x0100
#define PUERTO_ETHERNET_TRDP    17224
#define MAX_TAM_DATOS_PD        1436
#define MAX_TAM_DATOS_MD        65535
#define TAM_CABECERA_PD         40

// Enumeración de tipos de mensaje TRDP
typedef enum {
    TIPO_MENSAJE_PD = 0x5064,
    TIPO_MENSAJE_MD = 0x4D64,
    TIPO_MENSAJE_SOLICITUD = 0x5271,
    TIPO_MENSAJE_RESPUESTA = 0x5270
} TipoMensajeTRDP;

// Enumeración de modos de comunicación
typedef enum {
    MODO_COMUNICACION_PD = 0,
    MODO_COMUNICACION_MD = 1
} ModoComunicacionTRDP;

// Códigos de error de la pila TRDP
typedef enum {
    EXITO_TRDP = 0,
    ERROR_INICIALIZACION_TRDP = -1,
    ERROR_CREACION_SOCKET = -2,
    ERROR_TIEMPO_ESPERA = -3,
    ERROR_VERIFICACION_CRC = -4,
    ERROR_PARAMETRO_INVALIDO = -5,
    ERROR_ASIGNACION_MEMORIA = -6
} CodigoErrorTRDP;

// Unión para representar direcciones IP en formato TRDP
typedef union {
    uint8_t  bytes[4];
    uint32_t palabra;
} DireccionIP_TRDP;

// Estructura de la cabecera PD (40 bytes)
typedef struct __attribute__((packed)) {
    uint32_t contador_secuencia;
    uint16_t version_protocolo;
    uint16_t tipo_mensaje;
    uint32_t identificador_comunicacion;
    uint32_t contador_topo_etb;
    uint32_t contador_topo_tren;
    uint32_t longitud_datos;
    uint32_t campo_reservado;
    uint32_t id_com_respuesta;
    uint32_t ip_respuesta;
    uint32_t checksum_cabecera;
} CabeceraPD_TRDP;

// Estructura de un trama PD completa
typedef struct {
    CabeceraPD_TRDP cabecera;
    uint8_t payload[MAX_TAM_DATOS_PD];
    uint32_t checksum_datos;
} TramaPD_TRDP;

// Estructura para manejar una sesión de comunicación TRDP
typedef struct {
    int fd_socket;
    DireccionIP_TRDP ip_propia;
    DireccionIP_TRDP ip_grupo_multicast;
    uint16_t puerto_asignado;
    bool sesion_activa;
    uint32_t seq_actual;
    uint32_t timestamp_ultimo_tx;
    uint32_t timestamp_ultimo_rx;
    void (*handler_rx)(uint32_t cid, uint8_t* datos, uint32_t len);
} SesionTRDP;

// Configuración de un publicador de datos
typedef struct {
    uint32_t cid;
    DireccionIP_TRDP ip_destino;
    uint32_t periodo_transmision_us;
    uint32_t tam_datos;
    uint8_t* buffer_datos;
    bool habilitado;
} PublicadorTRDP;

// Configuración de un suscriptor de datos
typedef struct {
    uint32_t cid;
    DireccionIP_TRDP ip_origen_filtro;
    uint32_t timeout_rx_us;
    uint32_t tam_datos_esperado;
    bool habilitado;
    uint32_t ultimo_seq_recibido;
    uint32_t errores_deteccion;
} SuscriptorTRDP;

#endif</stdbool.h></stdint.h>

Implementación de la Pila TRDP (pila_trdp.c)

#include "pila_trdp.h"
#include "lwip/udp.h"
#include "lwip/netif.h"
#include "calculadora_crc.h"
#include "stm32f4xx_hal.h"

static SesionTRDP g_sesion_actual;
static PublicadorTRDP g_lista_publicadores[16];
static SuscriptorTRDP g_lista_suscriptores[16];
static uint8_t g_indice_pub = 0;
static uint8_t g_indice_sub = 0;

CodigoErrorTRDP iniciar_sesion_trdp(DireccionIP_TRDP mi_ip, uint16_t mi_puerto)
{
    struct udp_pcb *nuevo_pcb;
    err_t resultado_lwip;
    
    nuevo_pcb = udp_new();
    if (!nuevo_pcb) {
        return ERROR_CREACION_SOCKET;
    }
    
    resultado_lwip = udp_bind(nuevo_pcb, IP_ADDR_ANY, mi_puerto);
    if (resultado_lwip != ERR_OK) {
        udp_remove(nuevo_pcb);
        return ERROR_CREACION_SOCKET;
    }
    
    udp_recv(nuevo_pcb, manejador_paquetes_entrantes, NULL);
    
    memset(&g_sesion_actual, 0, sizeof(SesionTRDP));
    g_sesion_actual.fd_socket = (int)nuevo_pcb;
    g_sesion_actual.ip_propia = mi_ip;
    g_sesion_actual.puerto_asignado = mi_puerto;
    g_sesion_actual.sesion_activa = true;
    g_sesion_actual.seq_actual = 0;
    g_sesion_actual.timestamp_ultimo_tx = HAL_GetTick();
    g_sesion_actual.timestamp_ultimo_rx = HAL_GetTick();
    
    g_indice_pub = 0;
    g_indice_sub = 0;
    
    return EXITO_TRDP;
}

CodigoErrorTRDP registrar_publicador(PublicadorTRDP* configuracion)
{
    if (g_indice_pub >= 16 || !configuracion->buffer_datos || configuracion->tam_datos == 0) {
        return ERROR_PARAMETRO_INVALIDO;
    }
    
    g_lista_publicadores[g_indice_pub] = *configuracion;
    g_lista_publicadores[g_indice_pub].habilitado = true;
    g_indice_pub++;
    
    return EXITO_TRDP;
}

CodigoErrorTRDP registrar_suscriptor(SuscriptorTRDP* configuracion)
{
    if (g_indice_sub >= 16 || configuracion->tam_datos_esperado == 0) {
        return ERROR_PARAMETRO_INVALIDO;
    }
    
    g_lista_suscriptores[g_indice_sub] = *configuracion;
    g_lista_suscriptores[g_indice_sub].habilitado = true;
    g_lista_suscriptores[g_indice_sub].ultimo_seq_recibido = 0xFFFFFFFF;
    g_indice_sub++;
    
    return EXITO_TRDP;
}

CodigoErrorTRDP transmitir_datos_pd(uint32_t cid_objetivo, uint8_t* informacion, uint32_t cantidad)
{
    TramaPD_TRDP trama_envio;
    struct udp_pcb *pcb_activo = (struct udp_pcb *)g_sesion_actual.fd_socket;
    ip_addr_t ip_objetivo;
    
    if (!g_sesion_actual.sesion_activa) return ERROR_INICIALIZACION_TRDP;
    
    PublicadorTRDP* pub_encontrado = NULL;
    for (int i = 0; i < g_indice_pub; i++) {
        if (g_lista_publicadores[i].cid == cid_objetivo && g_lista_publicadores[i].habilitado) {
            pub_encontrado = &g_lista_publicadores[i];
            break;
        }
    }
    if (!pub_encontrado) return ERROR_PARAMETRO_INVALIDO;
    
    trama_envio.cabecera.contador_secuencia = g_sesion_actual.seq_actual++;
    trama_envio.cabecera.version_protocolo = VERSION_PROTO_TRDP;
    trama_envio.cabecera.tipo_mensaje = TIPO_MENSAJE_PD;
    trama_envio.cabecera.identificador_comunicacion = cid_objetivo;
    trama_envio.cabecera.longitud_datos = cantidad;
    trama_envio.cabecera.checksum_cabecera = calcular_checksum_crc32((uint8_t*)&trama_envio.cabecera, TAM_CABECERA_PD - 4);
    
    if (cantidad > MAX_TAM_DATOS_PD) return ERROR_PARAMETRO_INVALIDO;
    memcpy(trama_envio.payload, informacion, cantidad);
    trama_envio.checksum_datos = calcular_checksum_crc32(trama_envio.payload, cantidad);
    
    ip_objetivo.addr = pub_encontrado->ip_destino.palabra;
    struct pbuf *paquete = pbuf_alloc(PBUF_TRANSPORT, TAM_CABECERA_PD + cantidad + 4, PBUF_RAM);
    
    err_t estado_envio = udp_sendto(pcb_activo, paquete, &ip_objetivo, PUERTO_ETHERNET_TRDP);
    if (estado_envio != ERR_OK) return ERROR_CREACION_SOCKET;
    
    g_sesion_actual.timestamp_ultimo_tx = HAL_GetTick();
    return EXITO_TRDP;
}

void manejador_paquetes_entrantes(void *arg, struct udp_pcb *pcb,
                                 struct pbuf *paquete, const ip_addr_t *origen, u16_t puerto_origen)
{
    CabeceraPD_TRDP *cabecera_rx = (CabeceraPD_TRDP*)paquete->payload;
    uint8_t *datos_rx = (uint8_t*)paquete->payload + TAM_CABECERA_PD;
    
    if (paquete->tot_len < TAM_CABECERA_PD + 4) goto liberar;
    if (cabecera_rx->version_protocolo != VERSION_PROTO_TRDP) goto liberar;
    if (cabecera_rx->tipo_mensaje != TIPO_MENSAJE_PD) goto liberar;
    
    uint32_t crc_cabecera_calc = calcular_checksum_crc32((uint8_t*)cabecera_rx, TAM_CABECERA_PD - 4);
    if (crc_cabecera_calc != cabecera_rx->checksum_cabecera) goto liberar;
    
    uint32_t crc_datos_recibido = *((uint32_t*)(datos_rx + cabecera_rx->longitud_datos));
    uint32_t crc_datos_calc = calcular_checksum_crc32(datos_rx, cabecera_rx->longitud_datos);
    if (crc_datos_calc != crc_datos_recibido) goto liberar;
    
    for (int i = 0; i < g_indice_sub; i++) {
        if (g_lista_suscriptores[i].cid == cabecera_rx->identificador_comunicacion && g_lista_suscriptores[i].habilitado) {
            if (g_lista_suscriptores[i].ultimo_seq_recibido != 0xFFFFFFFF) {
                if (cabecera_rx->contador_secuencia != g_lista_suscriptores[i].ultimo_seq_recibido + 1) {
                    g_lista_suscriptores[i].errores_deteccion++;
                }
            }
            g_lista_suscriptores[i].ultimo_seq_recibido = cabecera_rx->contador_secuencia;
            if (g_sesion_actual.handler_rx) {
                g_sesion_actual.handler_rx(cabecera_rx->identificador_comunicacion, datos_rx, cabecera_rx->longitud_datos);
            }
            break;
        }
    }
    g_sesion_actual.timestamp_ultimo_rx = HAL_GetTick();
    
liberar:
    pbuf_free(paquete);
}

void asignar_callback_recepcion(void (*nuevo_handler)(uint32_t cid, uint8_t* datos, uint32_t len))
{
    g_sesion_actual.handler_rx = nuevo_handler;
}

void rutina_temporizada_trdp(void)
{
    uint32_t tiempo_actual = HAL_GetTick();
    
    for (int i = 0; i < g_indice_sub; i++) {
        if (g_lista_suscriptores[i].habilitado && 
            (tiempo_actual - g_sesion_actual.timestamp_ultimo_rx > g_lista_suscriptores[i].timeout_rx_us / 1000)) {
            g_lista_suscriptores[i].errores_deteccion++;
        }
    }
    
    for (int i = 0; i < g_indice_pub; i++) {
        if (g_lista_publicadores[i].habilitado &&
            (tiempo_actual - g_sesion_actual.timestamp_ultimo_tx >= g_lista_publicadores[i].periodo_transmision_us / 1000)) {
            transmitir_datos_pd(g_lista_publicadores[i].cid, 
                               g_lista_publicadores[i].buffer_datos, 
                               g_lista_publicadores[i].tam_datos);
        }
    }
}

CodigoErrorTRDP unirse_a_grupo_multicast(DireccionIP_TRDP ip_grupo)
{
    ip_addr_t grupo;
    struct netif *interfaz = netif_default;
    
    if (!g_sesion_actual.sesion_activa || !interfaz) return ERROR_INICIALIZACION_TRDP;
    
    grupo.addr = ip_grupo.palabra;
    igmp_joingroup(interfaz, &grupo);
    g_sesion_actual.ip_grupo_multicast = ip_grupo;
    return EXITO_TRDP;
}

Calculadora de Checksum CRC32 (calculadora_crc.c)

#include "calculadora_crc.h"

#define POLINOMIO_CRC 0xEDB88320

uint32_t calcular_checksum_crc32(uint8_t* bloque_datos, uint32_t tamanio)
{
    uint32_t checksum = 0xFFFFFFFF;
    for (uint32_t idx = 0; idx < tamanio; idx++) {
        checksum ^= bloque_datos[idx];
        for (int bit = 0; bit < 8; bit++) {
            checksum = (checksum >> 1) ^ ((checksum & 1) ? POLINOMIO_CRC : 0);
        }
    }
    return ~checksum;
}

uint32_t crc32_optimizado(uint8_t* datos, uint32_t longitud)
{
    static const uint32_t tabla_lut[256] = {
        0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
        // ... tabla completa de 256 elementos
    };
    uint32_t valor_crc = 0xFFFFFFFF;
    for (uint32_t pos = 0; pos < longitud; pos++) {
        valor_crc = (valor_crc >> 8) ^ tabla_lut[(valor_crc ^ datos[pos]) & 0xFF];
    }
    return ~valor_crc;
}

Aplicación de Ejemplo para Control Ferroviario (control_tren.c)

#include "control_tren.h"
#include "pila_trdp.h"

typedef struct {
    uint32_t identificador_tren;
    uint8_t  estado_puertas[8];
    uint8_t  sistema_frenado;
    uint8_t  sistema_traccion;
    uint16_t velocidad_actual;
    uint16_t tension_catenaria;
    uint16_t tension_bateria;
    uint32_t odometro;
    uint8_t  registro_fallos[16];
} EstadoVehiculo;

typedef struct {
    uint8_t  orden_puertas;
    uint8_t  orden_frenado;
    uint8_t  orden_traccion;
    uint16_t velocidad_maxima;
    uint8_t  activar_freno_emergencia;
} OrdenControl;

EstadoVehiculo g_estado_actual;
OrdenControl g_orden_pendiente;

#define CID_ESTADO_VEHICULO  0x1001
#define CID_ORDEN_CONTROL    0x1002
#define CID_DETALLES_PUERTAS 0x2001
#define CID_DETALLES_FRENOS  0x2002

CodigoErrorTRDP configurar_comunicacion_tren(void)
{
    DireccionIP_TRDP ip_local = {.bytes = {192, 168, 10, 50}};
    DireccionIP_TRDP ip_multicast = {.bytes = {239, 192, 10, 5}};
    
    if (iniciar_sesion_trdp(ip_local, PUERTO_ETHERNET_TRDP) != EXITO_TRDP) {
        return ERROR_INICIALIZACION_TRDP;
    }
    
    unirse_a_grupo_multicast(ip_multicast);
    
    PublicadorTRDP pub_estado = {
        .cid = CID_ESTADO_VEHICULO,
        .ip_destino = ip_multicast,
        .periodo_transmision_us = 100000,
        .tam_datos = sizeof(EstadoVehiculo),
        .buffer_datos = (uint8_t*)&g_estado_actual
    };
    registrar_publicador(&pub_estado);
    
    SuscriptorTRDP sub_ordenes = {
        .cid = CID_ORDEN_CONTROL,
        .timeout_rx_us = 500000,
        .tam_datos_esperado = sizeof(OrdenControl)
    };
    registrar_suscriptor(&sub_ordenes);
    
    asignar_callback_recepcion(procesar_mensaje_entrante);
    return EXITO_TRDP;
}

void procesar_mensaje_entrante(uint32_t cid, uint8_t* datos, uint32_t longitud)
{
    if (cid == CID_ORDEN_CONTROL && longitud == sizeof(OrdenControl)) {
        memcpy(&g_orden_pendiente, datos, sizeof(OrdenControl));
        ejecutar_orden(&g_orden_pendiente);
    }
}

void ejecutar_orden(OrdenControl* orden)
{
    if (orden->activar_freno_emergencia) {
        activar_secuencia_emergencia();
        return;
    }
    controlar_puertas(orden->orden_puertas);
    controlar_frenado(orden->orden_frenado);
    controlar_traccion(orden->orden_traccion);
    ajustar_limite_velocidad(orden->velocidad_maxima);
}

void actualizar_lecturas_sensores(void)
{
    g_estado_actual.velocidad_actual = obtener_velocidad();
    g_estado_actual.tension_catenaria = obtener_tension_red();
    g_estado_actual.tension_bateria = obtener_tension_almacenamiento();
    for (int i = 0; i < 8; i++) {
        g_estado_actual.estado_puertas[i] = consultar_puerta(i);
    }
    g_estado_actual.sistema_frenado = estado_frenos();
    g_estado_actual.sistema_traccion = estado_motores();
    g_estado_actual.odometro += calcular_avance();
    verificar_averias(&g_estado_actual);
}

void bucle_principal_aplicacion(void)
{
    static uint32_t ultimo_update = 0;
    uint32_t ahora = HAL_GetTick();
    if (ahora - ultimo_update >= 100) {
        actualizar_lecturas_sensores();
        ultimo_update = ahora;
    }
    rutina_temporizada_trdp();
}

Configuración del Entorno de Desarrollo

Parámetros del Proyecto en Keil uVision

Dispositivo objetivo: STM32F407ZGT6
Frecuencia del reloj: 8MHz HSE hasta 168MHz medianet PLL
Memoria disponible: 192KB SRAM, 1MB Flash
Definiciones del preprocesador: USE_STDPERIPH_DRIVER, STM32F407xx

Mapa de Memoria para el Enlazador

REGION_CARGA 0x08000000 0x00100000 {
  REGION_EJECUCION 0x08000000 0x00100000 {
    *.o (VECTOR, +Primero)
    *(SeccionesRaiz)
    .CUALQUIERA (+SoloLectura)
  }
  REGION_DATOS_RAM 0x20000000 0x00030000 {
    .CUALQUIERA (+LecturaEscritura +InicializadoCero)
  }
  REGION_RAM_EXTERNA 0xD0000000 0x00800000 {
    .CUALQUIERA (lwip_ram)
  }
}

Pruebas y Validación

Herramientas de Análisis

Analizador de protocolos: Wireshark con filtros TRDP
Herramienta específica: TRDP Spy para diagnóstico profundo
Simulador de red: Generador de tráfico para pruebas de carga

Casos de Prueba Representativos

void prueba_basica_comunicacion(void)
{
    uint8_t datos_test[8] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22};
    CodigoErrorTRDP resultado = transmitir_datos_pd(0x9999, datos_test, 8);
    if (resultado == EXITO_TRDP) {
        registrar_evento("Transmisión de prueba exitosa");
    }
}

void prueba_publicacion_estado(void)
{
    EstadoVehiculo estado_simulado;
    estado_simulado.identificador_tren = 0x12345678;
    estado_simulado.velocidad_actual = 600;
    estado_simulado.tension_catenaria = 2500;
    transmitir_datos_pd(CID_ESTADO_VEHICULO, (uint8_t*)&estado_simulado, sizeof(estado_simulado));
}

Mecanismos de Seguridad y Fiabilidad

Estrategias de Integridad de Datos

Verificación CRC32 en cabeceras y cargas útiles
Monitoreo de secuencia para detectar paquetes perdidos
Temporizadores de vigila para enlaces de comunicación
Configuración de redunadncia en la topología de red

Rutinas de Recuperación ante Fallos

void gestionar_fallo_comunicacion(void)
{
    registrar_alerta("Fallo en la comunicación TRDP");
    entrar_modo_seguro();
    if (iniciar_sesion_trdp(g_sesion_actual.ip_propia, g_sesion_actual.puerto_asignado) != EXITO_TRDP) {
        reiniciar_hardware();
    }
}

Etiquetas: STM32 TRDP C LWIP Ethernet industrial

Publicado el 7-11 12:26