Introducción a la Transferencia Ymodem en Zynq
La integración del protocolo Ymodem en plataformas embebidas como el Xilinx Zynq permite la transferencia fiable de archivos a través de interfaces serie, como la UART. Este proceso es fundamental para tareas como la actualización de firmware o la descarga de datos de registro. El siguiente contenido describe una implementación del protocolo Ymodem en un entorno Zynq, basándose en la versión V2014.4 del SDK de Xilinx (ahora Vitis) y empleando una configuración estándar de UART. Si bien el protocolo soporta archivos de cualquier tamaño, es importante considerar que las velocidades de transferencia estarán limitadas por las características de la comunicación serie.
Funciones Auxiliares para Interacción UART en Zynq
Para la comunicación con el periférico UART del Procesamiento de Zynq (PS UART), se requieren funciones de bajo nivel que gestionen el envío y la recepción de bytes. Las implementaciones que se presentan a continuación son adaptaciones de llamadas a las APIs del controlador XUartPs, e incluyen mecanismos de retardo y sondeo para la sincronización.
#include <stdint.h>
#include "xuartps.h" // Se asume el uso del driver Xilinx UART PS
// Definiciones de los códigos de control del protocolo Ymodem
#define PROTOCOL_SOH 0x01 // Start Of Header (paquete de 128 bytes)
#define PROTOCOL_STX 0x02 // Start Of Text (paquete de 1024 bytes)
#define PROTOCOL_EOT 0x04 // End Of Transmission
#define PROTOCOL_ACK 0x06 // Acknowledge
#define PROTOCOL_NAK 0x15 // Negative Acknowledge
#define PROTOCOL_CAN 0x18 // Cancel
#define PROTOCOL_C 0x43 // Carácter 'C' para iniciar la transmisión CRC
// La dirección base de la UART, típicamente definida en el BSP de Xilinx
extern u32 STDOUT_BASEADDRESS;
// Buffers globales para la operación del protocolo
uint8_t g_dataPacketBuffer[1024]; // Buffer para los datos del paquete (SOH o STX)
char g_filenameBuffer[128]; // Para almacenar el nombre del archivo del paquete inicial
int g_expectedFileSizeBytes = 0; // Tamaño total del archivo esperado
/**
* @brief Envía un byte individual a través de la UART de Zynq.
* Incluye un pequeño retardo de espera activa para control de flujo o espaciado.
* @param byte_val El byte a transmitir.
*/
void uart_send_byte_blocking(uint8_t byte_val)
{
volatile int delay_iter;
XUartPs_SendByte(STDOUT_BASEADDRESS, byte_val);
// Retardo para control de sincronización
for(delay_iter = 0; delay_iter < 1500; delay_iter++) {
// Espera activa (busy-wait)
}
}
/**
* @brief Intenta leer un byte de la UART con un tiempo de espera.
* Se asume que 'MyUart_RecvByte' es una función de bajo nivel
* que devuelve un valor largo donde el byte recibido está en los 8 bits
* menos significativos, y los bits superiores indican estado (0 si hay datos).
* @param max_attempts Número de iteraciones para esperar un byte.
* @param received_char Puntero donde se almacenará el byte si se recibe.
* @return 0 si se recibió un byte exitosamente, 1 si hubo un timeout.
*/
unsigned char uart_read_byte_with_timeout(unsigned int max_attempts, unsigned char *received_char)
{
volatile int i;
unsigned long raw_uart_data;
// Un retardo inicial para alinearse con el comportamiento original
for(i = 0; i < max_attempts; i++) {
// Espera activa
}
// Bucle de sondeo para intentar leer el byte
for(i = 0; i < 1000; i++) {
raw_uart_data = MyUart_RecvByte(); // Función hipotética del entorno original
if( (raw_uart_data & 0xFFFFFF00) == 0 ) // Verifica si hay datos válidos
{
*received_char = (unsigned char)(raw_uart_data & 0x000000FF);
return 0; // Byte recibido
}
}
return 1; // Timeout: no se recibió ningún byte
}
/**
* @brief Lee un byte de la UART de forma bloqueante hasta un número limitado de intentos.
* @param received_char Puntero donde se almacenará el byte recibido.
* @return El byte recibido (valor > 0) o 0 si no se pudo leer.
*/
uint8_t uart_read_blocking(uint8_t *received_char)
{
volatile int i;
unsigned long raw_uart_data;
for(i = 0; i < 1000; i++) { // Número de intentos para la lectura
raw_uart_data = MyUart_RecvByte(); // Función hipotética del entorno original
if( (raw_uart_data & 0xFFFFFF00) == 0 )
{
*received_char = (unsigned char)(raw_uart_data & 0x000000FF);
return (*received_char);
}
}
return 0; // No se recibió ningún byte
}
// Declaraciones de funciones CRC16 y conversión de cadena a entero (asumidas externas)
extern uint16_t calculate_crc16(const uint8_t *data, int length);
extern void string_to_int_conversion(const uint8_t *str_ptr, int *int_val_ptr);
Mecanismo de Recepción de Archivos Ymodem
La función principal implementa una máquina de estados para manejar las distintas fases del protocolo Ymodem: la negocicaión inicial, la transferencia de datos en paquetes (SOH o STX) y la secuencia de finalización (EOT). Se incorpora la verificación de la suma de control CRC16 para cada paquete, lo que es esencial para la integridad de los datos. Sin embargo, se deben considerar las observaciones sobre posibles anomalías en la verificación del primer paquete.
/**
* @brief Procesa la recepción de un archivo utilizando el protocolo Ymodem.
* @return La cantidad total de bytes del archivo recibidos, o un código de error negativo.
*/
int process_ymodem_file_reception(void)
{
enum YmodemState {
STATE_INITIATION, // Estado inicial: esperando el primer paquete
STATE_DATA_TRANSFER, // Estado de transferencia de paquetes de datos
STATE_END_OF_TRANSMISSION // Estado final: procesando el cierre de la transferencia
} current_state = STATE_INITIATION;
uint8_t rx_byte;
int error_count = 0;
int crc_calculated_val;
int crc_received_val;
int current_packet_length = 0;
int total_bytes_accumulated = 0;
int i;
error_count = 0;
g_expectedFileSizeBytes = 0;
while(1) // Bucle principal del proceso de recepción
{
switch(current_state)
{
case STATE_INITIATION:
// Paso 1: Enviar 'C' para solicitar el inicio de la transmisión Ymodem (CRC16)
uart_send_byte_blocking(PROTOCOL_C);
// Esperar el primer carácter de respuesta del remitente con un timeout
if(uart_read_byte_with_timeout(1000000, &rx_byte) == 0) // Timeout de ~1 segundo
{
if(rx_byte == PROTOCOL_SOH)
{
// Paquete inicial (número 0) con el nombre del archivo y tamaño (128 bytes)
uart_read_blocking(&rx_byte); // Número de paquete (debe ser 0x00)
if(rx_byte != 0x00) return -1;
uart_read_blocking(&rx_byte); // Complemento del número de paquete (debe ser 0xFF)
if(rx_byte != 0xFF) return -2;
// Recibir los 128 bytes del paquete 0
for(i = 0; i < 128; i++)
{
uart_read_blocking(&g_dataPacketBuffer[i]);
}
// Recibir los 2 bytes de CRC16 para este paquete (puede que no se verifique en este punto)
uart_read_blocking(&rx_byte); // CRC16 MSB
uart_read_blocking(&rx_byte); // CRC16 LSB
// Nota: El CRC de este paquete a menudo falla en las implementaciones originales.
uart_send_byte_blocking(PROTOCOL_ACK); // Confirmar recepción del paquete 0
// Extraer el nombre del archivo y el tamaño
for(i = 0; i < 128; i++) g_filenameBuffer[i] = 0; // Limpiar buffer
for(i = 0; (i < 128) && (g_dataPacketBuffer[i] != '\0'); i++)
{
g_filenameBuffer[i] = g_dataPacketBuffer[i];
}
// El tamaño del archivo se encuentra como una cadena ASCII después del nombre
string_to_int_conversion((const uint8_t *)(g_dataPacketBuffer + i + 1), &g_expectedFileSizeBytes);
// Enviar otro 'C' para iniciar la transferencia de los paquetes de datos reales
uart_send_byte_blocking(PROTOCOL_C);
current_state = STATE_DATA_TRANSFER; // Transición al estado de datos
}
else if (rx_byte == PROTOCOL_CAN) {
// El remitente ha cancelado la operación
return -3;
}
}
else
{
// Timeout inicial, el remitente no ha respondido. Podríamos reintentar o abortar.
error_count++;
if (error_count > 5) return -4; // Demasiados intentos fallidos
}
break;
case STATE_DATA_TRANSFER:
uart_read_blocking(&rx_byte); // Esperar SOH, STX, EOT, CAN
switch(rx_byte)
{
case PROTOCOL_EOT:
current_state = STATE_END_OF_TRANSMISSION;
uart_send_byte_blocking(PROTOCOL_ACK); // ACK para el primer EOT
continue; // Continuar al siguiente estado
case PROTOCOL_SOH:
current_packet_length = 128;
break;
case PROTOCOL_STX:
current_packet_length = 1024;
break;
case PROTOCOL_CAN:
// El remitente ha enviado una señal de cancelación
return total_bytes_accumulated; // Retornar bytes recibidos hasta ahora
default:
// Carácter inesperado, posible error o cancelación no estándar
return total_bytes_accumulated;
}
// Recibir el número de paquete y su complemento
uart_read_blocking(&rx_byte); // Número de paquete
uart_read_blocking(&rx_byte); // Complemento
// Recibir los datos del paquete actual
for(i = 0; i < current_packet_length; i++)
{
uart_read_blocking(&g_dataPacketBuffer[i]);
}
// Recibir y verificar el CRC16
uart_read_blocking(&rx_byte); // CRC16 MSB
crc_received_val = rx_byte << 8;
uart_read_blocking(&rx_byte); // CRC16 LSB
crc_received_val |= rx_byte;
crc_calculated_val = calculate_crc16(g_dataPacketBuffer, current_packet_length);
if(crc_calculated_val != crc_received_val)
{
error_count++;
uart_send_byte_blocking(PROTOCOL_NAK); // Solicitar reenvío del paquete
// Implementación de reintentos por paquete no incluida por brevedad
}
else
{
error_count = 0; // Reiniciar contador de errores al recibir un paquete correctamente
uart_send_byte_blocking(PROTOCOL_ACK); // Confirmar recepción correcta
total_bytes_accumulated += current_packet_length;
// Aquí se deberían procesar o almacenar los datos en 'g_dataPacketBuffer'
}
break;
case STATE_END_OF_TRANSMISSION:
// Después de recibir el primer EOT y enviar ACK, el remitente enviará otro 'C'
// y un paquete SOH 0x00 0xFF final (metadatos o paquete de 0 bytes).
uart_send_byte_blocking(PROTOCOL_C);
if(uart_read_byte_with_timeout(1000000, &rx_byte) == 0)
{
if(rx_byte == PROTOCOL_SOH)
{
uart_read_blocking(&rx_byte); // Número de paquete (0x00)
if(rx_byte != 0x00) return total_bytes_accumulated;
uart_read_blocking(&rx_byte); // Complemento (0xFF)
if(rx_byte != 0xFF) return total_bytes_accumulated;
// Leer los 128 bytes (generalmente nulos para este paquete final)
for(i = 0; i < 128; i++)
{
uart_read_blocking(&g_dataPacketBuffer[i]);
}
// Verificar CRC16 para el paquete final
uart_read_blocking(&rx_byte);
crc_received_val = rx_byte << 8;
uart_read_blocking(&rx_byte);
crc_received_val |= rx_byte;
crc_calculated_val = calculate_crc16(g_dataPacketBuffer, 128); // Se asume longitud de 128
if(crc_calculated_val != crc_received_val)
{
error_count++;
uart_send_byte_blocking(PROTOCOL_NAK); // NAK si el CRC final falla
}
else
{
uart_send_byte_blocking(PROTOCOL_ACK); // ACK final, transferencia completada
return total_bytes_accumulated;
}
}
}
// Si hay timeout o un carácter inesperado en la fase final, terminar.
return total_bytes_accumulated;
}
}
return total_bytes_accumulated; // En caso de salida inesperada del bucle
}
Observaciones y Desafíos Identificados
Durante el proceso de desarrollo y prueba de esta implementación del protocolo Ymodem en Zynq, se han detectado los siguientes puntos que requieren considreación y posibles mejoras:
- Inconsistencia en la Verificación CRC16 del Paquete Inicial: Se ha observado que la suma de verificación CRC16 del primer paquete de datos, que contiene metadatos como el nombre y el tamaño del archivo, a menudo resulta incorrecta (por ejemplo, un valor de 0xFF). Curiosamente, el CRC de los paquetes de datos subsiguientes se calcula y verifica correctamente. Esto podría deberse a una interpretación ligeramente diferente de cómo se forma o calcula el CRC para este paquete específico, o a una desincronización inicial en el cálculo entre el emisor y el receptor.
- Variabilidad en la Compatibilidad con Software de Terminal: La solución funciona de manera fiable con Xshell6. Sin embargo, al utilizar otras aplicaciones de terminal como SuperTerminal o SecureCRT, se producen errores de datos en ciertos paquetes. Esta inconsistencia sugiere posibles diferencias en el manejo de temporizaciones, control de flujo o la interpretación de caracteres de control específicos del protocolo serie entre diferentes emuladores de terminal.
- Falta de Manejo de Cancelación por el Usuario: Cuando se intenta cancelar una transferencia de archivo a mitad de proceso, el sistema deja de responder y requeire un reinicio completo. Esto indica que la lógica de manejo de las señales de cancelación (como
PROTOCOL_CAN) o de errores inesperados durante la transferencia de datos no es robusta, lo que impide una salida limpia de la función de recepción. - Bloqueo de la Interfaz al Concluir la Transferencia: Después de una transferencia de archivo exitosa, la interfaz de usuario o la ejecución del programa principal pueden permanecer en un estado de espera, requiriendo que el usuario presione la tecla "Enter" para reanudar. Esto sugiere que el proceso de recepción Ymodem podría no estar liberando completamente el control o que la aplicación principal no está correctamente configurada para retomar su ejecución inmediatamente después de la finalización del protocolo.