Herramienta de cálculo CRC16 para el protocolo Modbus

Fundamentos matemáticos de CRC16

El cálculo CRC16 se fundamenta en una operación de división binaria conocida como división módulo 2. Esta operación se realiza sobre cadenas de bits, donde la sustracción se reemplaza por una operación XOR, sin considerar acarreos. Por ejemplo, la división módulo 2 de los números binarios 1101 y 1011 procede de la siguiente manera:

  1101
XOR 1011
--------
  0110  // Resultado de la operación XOR

En el contexto de CRC, el flujo de datos a verificar se trata como una secuencia de coeficientes de un polinomio binario D(x). Este polinomio se desplaza 16 posiciones a la izquierad y se divide, utilizando la división módulo 2, por un polinomio generador G(x) predefinido (como 0x8005). El residuo de esta división, R(x), es el valor CRC de 16 bits que se adjunta a los datos originales. El receptor repite el cálculo; si el residuo obtenido no es cero, se detecta un error en la transmisión.

La naturaleza de la división módulo 2 permite implementaciones eficientes mediante tablas precalculadas (lookup tables) u operaciones combinadas de desplazamiento y XOR.

El polinomio generador y su influencia

El polinomio generador es el elemento central que define las propiedades de detección de errores del algoritmo CRC16. Su selección determina la capacidad para identificar errores de bit único, dobles, en ráfaga y aquellos con un número impar de bits alterados.

Variantes comunes de CRC16

Existen múltiples estándares de CRC16, cada uno con su propio polinomio generador y parámetros asociados. Estos parámetros incluyen el valor inicial, la inversión de bits de entrada/salida y una máscara XOR final.

Estándar Polinomio (Hex) Valor Inicial Inv. Entrada Inv. Salida XOR Final
CRC-16-IBM 0x8005 0xFFFF No No 0x0000
CRC-16-CCITT 0x1021 0x1D0F 0x0000
CRC-16-MAXIM 0x8005 0x0000 0xFFFF
CRC-16-USB 0x8005 0xFFFF 0xFFFF

Nota: Aunque CRC-16-IBM y CRC-16-MAXIM comparten el mismo polinomio, sus otros parámetros difieren, produciendo resultados distintos.

Capacidad de detección de errores

Un polinomio generador de grado n (ej., 16 para CRC16) ofrece las siguientes garantías:

  • Error de un solo bit: Siempre detectable si el polinomio tiene al menos dos términos distintos de cero.
  • Error de doble bit: Detectable si el polinomio no es divisible por (x^a + x^b). La mayoría de los estándares cumplen esto.
  • Número impar de errores: Detectable si el polinomio contiene el factor (x+1).
  • Errores en ráfaga: Se pueden detectar ráfagas de longitud menor o igual a 16 bits con certeza. Para ráfagas más largas, la probabilidad de detección es extremadamente alta (1 - 2^{-16}).

Implementación de prueba de capacidad de detección

def calcular_crc16_ibm(datos: bytes) -> int:
    crc = 0xFFFF
    for byte in datos:
        crc ^= byte
        for _ in range(8):
            if crc &{Agrave} 0x0001:
                crc = (crc >> 1) ^ 0xA001  # 0x8005 invertido
            else:
                crc >>= 1
    return crc & 0xFFFF

# Datos de prueba
datos_originales = b'\x31\x32'
print(f"CRC original: {calcular_crc16_ibm(datos_originales):04X}")

# Simulación de errores
error_simple = b'\x31\x30'  # Cambio de un bit
print(f"CRC con error simple: {calcular_crc16_ibm(error_simple):04X}")

error_doble = b'\x30\x33'   # Cambio de dos bits
print(f"CRC con error doble: {calcular_crc16_ibm(error_doble):04X}")

error_rafaga = b'\x31\x3A'  # Cambio de 5 bits consecutivos
print(f"CRC con error en ráfaga: {calcular_crc16_ibm(error_rafaga):04X}")

Aplicación de CRC16 en el protocolo Modbus RTU

Modbus RTU emplea el estándar CRC-16-IBM (polinomio 0x8005, valor inicial 0xFFFF) para verificar la integridad de sus tramas.

Estructura de la trama y ubicación del CRC

Una trama Modbus RTU típica tiene la siguiente estructura secuencial:

[Dirección Esclavo (1 byte)][Código Función (1 byte)][Datos (N bytes)][CRC Low (1 byte)][CRC High (1 byte)]

El campo CRC, de 2 bytes, se sitúa al final. Modbus especifica que el byte menos significativo (LSB) del CRC se transmite primero, seguido del byte más significativo (MSB).

Proceso de cálculo y verificación

En el transmisor: Se calcula el CRC16 sobre todos los bytes de la trama excepto los dos últimos (que son el propio CRC). El resultado se descompone en sus bytes alto y bajo, y se añaden al final de la trama en el orden LSB, MSB.

En el receptor: Se calcula el CRC16 sobre todos los bytes recibidos excepto los dos últimos. Este valor calculado se compara con el CRC recibido (reensamblado a partir de los dos últimos bytes). Si coinciden, la trama se acepta.

Ejemplo de implementación en C

#include <stdint.h>

uint16_t calcular_crc_modbus(const uint8_t *datos, size_t longitud) {
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < longitud; ++i) {
        crc ^= datos[i];
        for (int j = 0; j < 8; ++j) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001; // Polinomio 0x8005 invertido
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

void adjuntar_crc(uint8_t *trama, size_t longitud_datos) {
    uint16_t crc = calcular_crc_modbus(trama, longitud_datos);
    trama[longitud_datos]   = (uint8_t)(crc & 0xFF);       // Byte bajo (LSB)
    trama[longitud_datos+1] = (uint8_t)((crc >> 8) & 0xFF); // Byte alto (MSB)
}

int verificar_crc(const uint8_t *trama_rx, size_t longitud_total) {
    if (longitud_total < 3) return 0; // Trama demasiado corta

    uint16_t crc_recibido = (trama_rx[longitud_total - 1] << 8) | trama_rx[longitud_total - 2];
    uint16_t crc_calculado = calcular_crc_modbus(trama_rx, longitud_total - 2);

    return (crc_recibido == crc_calculado);
}

Optimización mediante tablas precalculadas

Para mejorar el rendimiento, especialmente en sistemas embebidos, se utiliza el método de tablas precalculadas (lookup table).

Generación de la tabla para CRC-16-IBM

uint16_t tabla_crc[256];

void inicializar_tabla_crc() {
    for (int i = 0; i < 256; i++) {
        uint16_t crc = i;
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001)
                crc = (crc >> 1) ^ 0xA001;
            else
                crc >>= 1;
        }
        tabla_crc[i] = crc;
    }
}

uint16_t crc16_rapido(const uint8_t *datos, size_t longitud) {
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < longitud; i++) {
        uint8_t indice = (crc ^ datos[i]) & 0xFF;
        crc = (crc >> 8) ^ tabla_crc[indice];
    }
    return crc;
}

Este método reduce la complejidad de procesar un byte de 8 operaciones bit a bit a una simple operación de índice y XOR.

Diseño de una herramienta de cálculo CRC16

Una herramienta práctica para el cálculo de CRC16 en el contexto Modbus debe facilitar la entrada de los parámetros típicos de una trama y ofrecer resultados inmediatos.

Modelado de parámetros y conversión a bytes

class SolicitudModbus:
    def __init__(self, dir_esclavo, cod_funcion, dir_inicio, num_registros):
        self.dir_esclavo = dir_esclavo & 0xFF
        self.cod_funcion = cod_funcion & 0xFF
        self.dir_inicio_alto = (dir_inicio >> 8) & 0xFF
        self.dir_inicio_bajo = dir_inicio & 0xFF
        self.num_reg_alto = (num_registros >> 8) & 0xFF
        self.num_reg_bajo = num_registros & 0xFF

    def a_bytes(self):
        return bytes([
            self.dir_esclavo,
            self.cod_funcion,
            self.dir_inicio_alto,
            self.dir_inicio_bajo,
            self.num_reg_alto,
            self.num_reg_bajo
        ])

# Ejemplo de uso
solicitud = SolicitudModbus(1, 3, 0x006B, 1)
trama_bytes = solicitud.a_bytes()  # b'\x01\x03\x00\x6B\x00\x01'
crc = calcular_crc16_ibm(trama_bytes)
print(f"CRC calculado: {crc:04X}")
trama_completa = trama_bytes + crc.to_bytes(2, byteorder='little')
print(f"Trama completa: {trama_completa.hex(' ').upper()}")

Consiedraciones para una interfaz de usuario

  • Entrada estructurada: Campos separados para dirección esclavo, código de función, dirección inicial y número de registros.
  • Cálculo en tiempo real: Actualización inmediata del resultado CRC y la trama completa al modificar cualquier parámetro.
  • Validación de entrada: Comprobación de rangos válidos (ej., dirección esclavo 1-247).
  • Soporte para procesamiento por lotes: Capacidad para calcular CRC para múltiples conjuntos de parámetros introducidos en formato tabla o CSV.
  • Opciones de visualización: Mostrar el CRC en formato hexadecimal, desglosado en bytes alto/bajo, y la trama completa lista para su uso.

Etiquetas: Modbus CRC16 Protocolos industriales Comunicación Serie Verificación de integridad

Publicado el 8-1 23:24