FSoE: ¿Cómo se construyen las tablas CRC?

El protocolo FSoE (Functional Safety over EtherCAT) utiliza un CRC de 16 bits para proteger sus mensajes de seguridad. Las implementaciones suelen basarse en dos tablas de búsqueda precomputadas de 256 entradas, denominadas convencionalmente CRC16_TABLE y CRC16_TABLE2. Este artículo explica exactamente cómo se derivan esas tablas a partir del polinomio del CRC: sin constantes mágicas escritas a mano, solo el polinomio y el algoritmo.

El polinomio

El CRC de FSoE se basa en el polinomio de 17 bits

$$ P(x) = x^{16} + x^{13} + x^{12} + x^{11} + x^{8} + x^{7} + x^{5} + x^{4} + x^{2} + x + 1 $$

que, empaquetado como valor hexadecimal con el término implícito $x^{16}$, es 0x139B7. La representación de 16 bits utilizada dentro del registro de desplazamiento (el polinomio sin el término $x^{16}$) es 0x39B7.

El CRC se procesa MSB-first (la dirección «normal» o no reflejada): cada bit del mensaje se toma del extremo más significativo del registro de trabajo, y el polinomio se XORa siempre que el bit desplazado por la parte superior sea 1.

Qué es una entrada de una tabla de búsqueda CRC

Una tabla de búsqueda CRC byte a byte permite procesar un byte completo (8 bits) con una sola consulta a la tabla en lugar de 8 desplazamientos de bit individuales. Para un CRC MSB-first de anchura $W = 16$, la entrada para el valor de byte $i$ es el resto de la división polinomial

$$ \text{table}[i] = \left( i \cdot x^{W + 8k} \right) \bmod P(x) $$

donde $k$ selecciona para qué posición de byte dentro de un grupo multibyte está pensada la tabla:

  • El factor $x^{W} = x^{16}$ tiene en cuenta la anchura de 16 bits del registro CRC (el resto tiene 16 bits de ancho).
  • El factor $x^{8k}$ tiene en cuenta que el byte está $k$ posiciones de byte más a la derecha (menos significativo) dentro del grupo, de modo que se necesitan $8k$ posiciones de bit adicionales de reducción.

Computacionalmente, esto se realiza cargando $i$ en el byte alto de un registro de 16 bits ($\text{crc} = i \ll 8$) y realizando después $8 + 8k$ desplazamientos a la izquierda, XORando el polinomio en el registro cada vez que el bit 15 (el bit a punto de salir por la parte superior) esté activo:

$$ \text{crc} \gets \begin{cases} (\text{crc} \ll 1) \oplus \texttt{0x39B7} & \text{si } \text{crc} \mathbin{\&} \texttt{0x8000} \\ \text{crc} \ll 1 & \text{en caso contrario} \end{cases} $$

Esto es exactamente la división larga de $i \cdot x^{8+8k}$ entre $P(x)$, conservando únicamente el resto de 16 bits.

Las dos tablas de FSoE

La implementación de FSoE utiliza dos tablas, correspondientes a dos valores distintos de $k$:

CRC16_TABLE — la tabla byte a byte ordinaria ($k = 0$)

$$ \text{CRC16\_TABLE}[i] = \left( i \cdot x^{16} \right) \bmod P(x) $$

Esta se calcula con 8 desplazamientos a la izquierda de $i \ll 8$. Es la tabla de búsqueda estándar utilizada para actualizar el CRC un byte cada vez:

$$ \text{crc} \gets (\text{crc} \ll 8) \oplus \text{CRC16\_TABLE}[\text{byte}] $$

CRC16_TABLE2 — la tabla slice-by-4 ($k = 3$)

$$ \text{CRC16\_TABLE2}[i] = \left( i \cdot x^{40} \right) \bmod P(x) $$

Esta se calcula con 32 desplazamientos a la izquierda de $i \ll 8$ (dado que $8 + 8 \cdot 3 = 32$). Es la tabla slice-by-4 para el byte menos significativo de un grupo de 4 bytes. De manera equivalente, es el resultado de aplicar la consulta ordinaria CRC16_TABLE cuatro veces partiendo del byte $i$; es decir, procesando el byte $i$ seguido de tres bytes a cero:

$$ \text{CRC16\_TABLE2}[i] = \underbrace{\text{process}(\ldots\text{process}(\text{process}(i \ll 8) \ll 8) \ll 8)}_{4 \text{ lookups}} $$

En un paso de actualización slice-by-4 que consume cuatro bytes a la vez, el CRC se actualiza como:

$$ \text{crc} \gets \text{CRC16\_TABLE2}[\text{crc}_{\text{lo}} \oplus b_0] \oplus \text{CRC16\_TABLE}[\text{crc}_{\text{hi}} \oplus b_1] \oplus \ldots $$

donde $b_0$ es el byte de entrada menos significativo (el que necesita más reducción adicional, de ahí $k=3$).

Programa generador

El siguiente programa en C genera ambas tablas a partir del polinomio 0x39B7 y las imprime como código fuente C. El núcleo es una única función, table_entry(i, shifts), que realiza la división larga polinomial MSB-first descrita anteriormente. CRC16_TABLE se construye con shifts = 8 ($k=0$) y CRC16_TABLE2 con shifts = 32 ($k=3$).

GenTables.c
/*
 * GenTables.c
 * ============
 *
 * Programa generador de las tablas de búsqueda CRC-16
 * de FSoE (Functional Safety over EtherCAT).
 *
 * Este programa, una vez compilado y ejecutado, imprime código fuente C para
 * un segundo archivo fuente (Tables.c) que contiene dos tablas de búsqueda
 * de 16 bits con 256 entradas cada una. Las tablas se derivan únicamente del
 * polinomio del CRC; en la salida generada no aparece ninguna constante
 * escrita a mano.
 *
 *
 * El CRC
 * ------
 *
 * El CRC de FSoE es un CRC de 16 bits que utiliza el polinomio de 17 bits
 *
 *     P(x) = x^16 + x^13 + x^12 + x^11 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
 *
 * que en forma hexadecimal empaquetada (con el término implícito x^16
 * eliminado) es
 *
 *     0x39B7   (16 bits)
 *
 * o, incluyendo el término x^16,
 *
 *     0x139B7  (17 bits).
 *
 * El CRC se procesa MSB-first (a veces llamado dirección «normal» o
 * «no reflejada»): cada bit del mensaje se toma del extremo más
 * significativo del registro de trabajo y el polinomio se XORa
 * cuando el bit desplazado hacia fuera es 1.
 *
 *
 * Cómo se generan las tablas
 * --------------------------
 *
 * Una tabla de búsqueda CRC permite procesar un byte completo (8 bits) con
 * una sola consulta a la tabla en lugar de 8 desplazamientos de bit
 * individuales.  Para un CRC MSB-first, la entrada para el valor de byte `i`
 * es el resto de la división polinomial
 *
 *     table[i] = ( i * x^(16 + 8*k) ) mod P(x)
 *
 * donde `k` selecciona para qué posición de byte dentro de un grupo
 * multibyte está pensada la tabla.  El factor x^16 tiene en cuenta la
 * anchura de 16 bits del registro CRC (el resto tiene 16 bits de ancho), y
 * el factor x^(8*k) tiene en cuenta que el byte está `k` posiciones de byte
 * más a la derecha (menos significativo) dentro del grupo, de modo que se
 * necesitan `8*k` posiciones de bit adicionales de reducción.
 *
 * Computacionalmente esto se realiza cargando `i` en el byte alto de un
 * registro de 16 bits (crc = i << 8) y realizando después `8 + 8*k`
 * desplazamientos a la izquierda, XORando el polinomio en el registro cada
 * vez que el bit a punto de salir por la parte superior (bit 15) esté
 * activo:
 *
 *     for (n = 0; n < 8 + 8*k; n++)
 *         crc = (crc & 0x8000) ? ((crc << 1) ^ POLY) : (crc << 1);
 *
 * Las dos tablas generadas aquí son:
 *
 *   CRC16_TABLE   (k = 0):  table[i] = (i * x^16) mod P    --  8 desplazamientos
 *       Esta es la tabla de búsqueda byte a byte ordinaria.  Se usa para
 *       actualizar el CRC un byte cada vez:
 *           crc = (crc << 8) ^ CRC16_TABLE[byte];
 *
 *   CRC16_TABLE2  (k = 3):  table[i] = (i * x^40) mod P    -- 32 desplazamientos
 *       Esta es la tabla slice-by-4 para el byte menos significativo de un
 *       grupo de 4 bytes.  De manera equivalente, es el resultado de aplicar
 *       la consulta ordinaria a la tabla cuatro veces partiendo del byte `i`
 *       (es decir, procesando el byte `i` seguido de tres bytes a cero).  Se
 *       usa en el paso de actualización slice-by-4 que consume cuatro bytes
 *       a la vez:
 *           crc ^= (input32 & 0xFF);                       // incorpora el
 *           crc  = CRC16_TABLE2[crc & 0xFF]                // byte menos sig.
 *                ^ CRC16_TABLE [(input32 >> 8)  & 0xFF]    // ...luego los
 *                ^ <slice k=1 table>[(input32 >> 16) & 0xFF]   // bytes
 *                ^ <slice k=2 table>[(input32 >> 24) & 0xFF]   // restantes.
 *       (La implementación de FSoE solo requiere CRC16_TABLE y CRC16_TABLE2;
 *       las tablas slice k=1 y k=2 no se generan aquí.)
 *
 * Compilar y ejecutar:
 *     cc -std=c99 -O2 -o GenTables GenTables.c
 *     ./GenTables > Tables.c
 */

#include <stdint.h>
#include <stdio.h>

/* Bits bajos del polinomio 0x139B7 (el término x^16 es implícito). */
#define POLY 0x39B7U

/*
 * Calcula una entrada de la tabla CRC-16 MSB-first.
 *
 *   i      : el valor de byte (0..255) usado como índice de la tabla.
 *   shifts : el número de desplazamientos a la izquierda a realizar = 8 + 8*k,
 *            donde k es la posición del byte dentro de un slice de 4 bytes
 *            (k=0 -> 8 desplazamientos, k=3 -> 32 desplazamientos).
 *
 * El byte se coloca en el octeto alto de un registro de 16 bits y se
 * desplaza a la izquierda `shifts` veces.  Siempre que el bit superior
 * (bit 15) esté activo antes de un desplazamiento, el polinomio se XORa
 * después, lo cual es exactamente la división larga de (i * x^(8+shifts))
 * entre P(x) conservando únicamente el resto de 16 bits.
 */
static uint16_t table_entry(uint8_t i, int shifts)
{
    uint16_t crc = (uint16_t)i << 8;
    int n;
    for (n = 0; n < shifts; ++n) {
        if (crc & 0x8000U)
            crc = (uint16_t)((crc << 1) ^ POLY);
        else
            crc = (uint16_t)(crc << 1);
    }
    return crc;
}

/*
 * Rellena una tabla de 256 entradas calculando cada entrada de forma
 * independiente.  `shifts` selecciona qué tabla slice se construye
 * (8 para k=0, 32 para k=3).
 */
static void build_table(uint16_t table[256], int shifts)
{
    int i;
    for (i = 0; i < 256; ++i)
        table[i] = table_entry((uint8_t)i, shifts);
}

/* Imprime una tabla uint16_t de 256 entradas como código fuente C, 8 valores
 * por fila. */
static void print_table(const char *name, const uint16_t table[256])
{
    int i;
    printf("const uint16_t %s[256] = {\n", name);
    for (i = 0; i < 256; i += 8) {
        printf("    0x%04X, 0x%04X, 0x%04X, 0x%04X, "
               "0x%04X, 0x%04X, 0x%04X, 0x%04X,\n",
               table[i],   table[i+1], table[i+2], table[i+3],
               table[i+4], table[i+5], table[i+6], table[i+7]);
    }
    printf("};\n");
}

int main(void)
{
    uint16_t table1[256]; /* k = 0: (i * x^16) mod P */
    uint16_t table2[256]; /* k = 3: (i * x^40) mod P */

    build_table(table1, 8);   /* tabla byte a byte ordinaria            */
    build_table(table2, 32);  /* slice-by-4, byte menos significativo   */

    /*
     * Emite el segundo archivo fuente (Tables.c).  El comentario de cabecera
     * escrito aquí documenta exactamente qué son las tablas y cómo se
     * relacionan con el polinomio, de modo que Tables.c sea autónomo.
     */
    printf("/*\n");
    printf(" * Tables.c  (auto-generado por GenTables.c -- no editar)\n");
    printf(" * =======================================================\n");
    printf(" *\n");
    printf(" * Dos tablas de búsqueda CRC de 16 bits con 256 entradas para el\n");
    printf(" * CRC-16 de FSoE (Functional Safety over EtherCAT).\n");
    printf(" *\n");
    printf(" * Polinomio\n");
    printf(" * ----------\n");
    printf(" *   P(x) = x^16 + x^13 + x^12 + x^11 + x^8 + x^7 + x^5\n");
    printf(" *          + x^4 + x^2 + x + 1\n");
    printf(" *   empaquetado (sin el término x^16): 0x39B7\n");
    printf(" *   empaquetado (con el término x^16):  0x139B7\n");
    printf(" *\n");
    printf(" * Dirección: MSB-first («normal», no reflejada).\n");
    printf(" *\n");
    printf(" * Construcción de las tablas\n");
    printf(" * --------------------------\n");
    printf(" * Cada entrada es el resto de una división polinomial:\n");
    printf(" *\n");
    printf(" *   table[i] = ( i * x^(16 + 8*k) ) mod P(x)\n");
    printf(" *\n");
    printf(" * calculado cargando i en el byte alto de un registro de 16\n");
    printf(" * bits (crc = i << 8) y realizando 8 + 8*k desplazamientos a\n");
    printf(" * la izquierda, XORando el polinomio siempre que el bit 15\n");
    printf(" * esté activo antes de un desplazamiento:\n");
    printf(" *\n");
    printf(" *   for (n = 0; n < 8 + 8*k; n++)\n");
    printf(" *       crc = (crc & 0x8000) ? ((crc << 1) ^ 0x39B7)\n");
    printf(" *                             :  (crc << 1);\n");
    printf(" *\n");
    printf(" * Tablas en este archivo\n");
    printf(" * ----------------------\n");
    printf(" * CRC16_TABLE   (k = 0,  8 desplazamientos):\n");
    printf(" *   table[i] = (i * x^16) mod P.\n");
    printf(" *   Tabla de búsqueda byte a byte ordinaria.  Se usa para\n");
    printf(" *   actualizar el CRC un byte cada vez:\n");
    printf(" *       crc = (crc << 8) ^ CRC16_TABLE[byte];\n");
    printf(" *\n");
    printf(" * CRC16_TABLE2  (k = 3, 32 desplazamientos):\n");
    printf(" *   table[i] = (i * x^40) mod P.\n");
    printf(" *   Tabla slice-by-4 para el byte menos significativo de un\n");
    printf(" *   grupo de 4 bytes; de manera equivalente, el resultado de\n");
    printf(" *   aplicar la consulta ordinaria a la tabla cuatro veces\n");
    printf(" *   partiendo del byte i (byte i seguido de tres bytes a\n");
    printf(" *   cero).\n");
    printf(" */\n");
    printf("\n");
    printf("#include <stdint.h>\n");
    printf("\n");

    print_table("CRC16_TABLE",  table1);
    printf("\n");
    print_table("CRC16_TABLE2", table2);

    return 0;
}

Compílalo y ejecútalo:

Build commands
cc -std=c99 -O2 -o GenTables GenTables.c
./GenTables > Tables.c

Tablas generadas

La salida del generador es el siguiente archivo fuente C autónomo. Ambas tablas coinciden exactamente con las tablas de referencia de FSoE: cada una de las 256 entradas de cada tabla.

Tables.c
/*
 * Tables.c  (auto-generado por GenTables.c -- no editar)
 * =======================================================
 *
 * Dos tablas de búsqueda CRC de 16 bits con 256 entradas para el
 * CRC-16 de FSoE (Functional Safety over EtherCAT).
 *
 * Polinomio
 * ----------
 *   P(x) = x^16 + x^13 + x^12 + x^11 + x^8 + x^7 + x^5
 *          + x^4 + x^2 + x + 1
 *   empaquetado (sin el término x^16): 0x39B7
 *   empaquetado (con el término x^16):  0x139B7
 *
 * Dirección: MSB-first («normal», no reflejada).
 *
 * Construcción de las tablas
 * --------------------------
 * Cada entrada es el resto de una división polinomial:
 *
 *   table[i] = ( i * x^(16 + 8*k) ) mod P(x)
 *
 * calculado cargando i en el byte alto de un registro de 16 bits
 * (crc = i << 8) y realizando 8 + 8*k desplazamientos a la
 * izquierda, XORando el polinomio siempre que el bit 15 esté activo
 * antes de un desplazamiento:
 *
 *   for (n = 0; n < 8 + 8*k; n++)
 *       crc = (crc & 0x8000) ? ((crc << 1) ^ 0x39B7)
 *                             :  (crc << 1);
 *
 * Tablas en este archivo
 * ----------------------
 * CRC16_TABLE   (k = 0,  8 desplazamientos):
 *   table[i] = (i * x^16) mod P.
 *   Tabla de búsqueda byte a byte ordinaria.  Se usa para actualizar el
 *   CRC un byte cada vez:
 *       crc = (crc << 8) ^ CRC16_TABLE[byte];
 *
 * CRC16_TABLE2  (k = 3, 32 desplazamientos):
 *   table[i] = (i * x^40) mod P.
 *   Tabla slice-by-4 para el byte menos significativo de un grupo de 4
 *   bytes; de manera equivalente, el resultado de aplicar la consulta
 *   ordinaria a la tabla cuatro veces partiendo del byte i (byte i
 *   seguido de tres bytes a cero).
 */

#include <stdint.h>

const uint16_t CRC16_TABLE[256] = {
    0x0000, 0x39B7, 0x736E, 0x4AD9, 0xE6DC, 0xDF6B, 0x95B2, 0xAC05,
    0xF40F, 0xCDB8, 0x8761, 0xBED6, 0x12D3, 0x2B64, 0x61BD, 0x580A,
    0xD1A9, 0xE81E, 0xA2C7, 0x9B70, 0x3775, 0x0EC2, 0x441B, 0x7DAC,
    0x25A6, 0x1C11, 0x56C8, 0x6F7F, 0xC37A, 0xFACD, 0xB014, 0x89A3,
    0x9AE5, 0xA352, 0xE98B, 0xD03C, 0x7C39, 0x458E, 0x0F57, 0x36E0,
    0x6EEA, 0x575D, 0x1D84, 0x2433, 0x8836, 0xB181, 0xFB58, 0xC2EF,
    0x4B4C, 0x72FB, 0x3822, 0x0195, 0xAD90, 0x9427, 0xDEFE, 0xE749,
    0xBF43, 0x86F4, 0xCC2D, 0xF59A, 0x599F, 0x6028, 0x2AF1, 0x1346,
    0x0C7D, 0x35CA, 0x7F13, 0x46A4, 0xEAA1, 0xD316, 0x99CF, 0xA078,
    0xF872, 0xC1C5, 0x8B1C, 0xB2AB, 0x1EAE, 0x2719, 0x6DC0, 0x5477,
    0xDDD4, 0xE463, 0xAEBA, 0x970D, 0x3B08, 0x02BF, 0x4866, 0x71D1,
    0x29DB, 0x106C, 0x5AB5, 0x6302, 0xCF07, 0xF6B0, 0xBC69, 0x85DE,
    0x9698, 0xAF2F, 0xE5F6, 0xDC41, 0x7044, 0x49F3, 0x032A, 0x3A9D,
    0x6297, 0x5B20, 0x11F9, 0x284E, 0x844B, 0xBDFC, 0xF725, 0xCE92,
    0x4731, 0x7E86, 0x345F, 0x0DE8, 0xA1ED, 0x985A, 0xD283, 0xEB34,
    0xB33E, 0x8A89, 0xC050, 0xF9E7, 0x55E2, 0x6C55, 0x268C, 0x1F3B,
    0x18FA, 0x214D, 0x6B94, 0x5223, 0xFE26, 0xC791, 0x8D48, 0xB4FF,
    0xECF5, 0xD542, 0x9F9B, 0xA62C, 0x0A29, 0x339E, 0x7947, 0x40F0,
    0xC953, 0xF0E4, 0xBA3D, 0x838A, 0x2F8F, 0x1638, 0x5CE1, 0x6556,
    0x3D5C, 0x04EB, 0x4E32, 0x7785, 0xDB80, 0xE237, 0xA8EE, 0x9159,
    0x821F, 0xBBA8, 0xF171, 0xC8C6, 0x64C3, 0x5D74, 0x17AD, 0x2E1A,
    0x7610, 0x4FA7, 0x057E, 0x3CC9, 0x90CC, 0xA97B, 0xE3A2, 0xDA15,
    0x53B6, 0x6A01, 0x20D8, 0x196F, 0xB56A, 0x8CDD, 0xC604, 0xFFB3,
    0xA7B9, 0x9E0E, 0xD4D7, 0xED60, 0x4165, 0x78D2, 0x320B, 0x0BBC,
    0x1487, 0x2D30, 0x67E9, 0x5E5E, 0xF25B, 0xCBEC, 0x8135, 0xB882,
    0xE088, 0xD93F, 0x93E6, 0xAA51, 0x0654, 0x3FE3, 0x753A, 0x4C8D,
    0xC52E, 0xFC99, 0xB640, 0x8FF7, 0x23F2, 0x1A45, 0x509C, 0x692B,
    0x3121, 0x0896, 0x424F, 0x7BF8, 0xD7FD, 0xEE4A, 0xA493, 0x9D24,
    0x8E62, 0xB7D5, 0xFD0C, 0xC4BB, 0x68BE, 0x5109, 0x1BD0, 0x2267,
    0x7A6D, 0x43DA, 0x0903, 0x30B4, 0x9CB1, 0xA506, 0xEFDF, 0xD668,
    0x5FCB, 0x667C, 0x2CA5, 0x1512, 0xB917, 0x80A0, 0xCA79, 0xF3CE,
    0xABC4, 0x9273, 0xD8AA, 0xE11D, 0x4D18, 0x74AF, 0x3E76, 0x07C1,
};

const uint16_t CRC16_TABLE2[256] = {
    0x0000, 0x7648, 0xEC90, 0x9AD8, 0xE097, 0x96DF, 0x0C07, 0x7A4F,
    0xF899, 0x8ED1, 0x1409, 0x6241, 0x180E, 0x6E46, 0xF49E, 0x82D6,
    0xC885, 0xBECD, 0x2415, 0x525D, 0x2812, 0x5E5A, 0xC482, 0xB2CA,
    0x301C, 0x4654, 0xDC8C, 0xAAC4, 0xD08B, 0xA6C3, 0x3C1B, 0x4A53,
    0xA8BD, 0xDEF5, 0x442D, 0x3265, 0x482A, 0x3E62, 0xA4BA, 0xD2F2,
    0x5024, 0x266C, 0xBCB4, 0xCAFC, 0xB0B3, 0xC6FB, 0x5C23, 0x2A6B,
    0x6038, 0x1670, 0x8CA8, 0xFAE0, 0x80AF, 0xF6E7, 0x6C3F, 0x1A77,
    0x98A1, 0xEEE9, 0x7431, 0x0279, 0x7836, 0x0E7E, 0x94A6, 0xE2EE,
    0x68CD, 0x1E85, 0x845D, 0xF215, 0x885A, 0xFE12, 0x64CA, 0x1282,
    0x9054, 0xE61C, 0x7CC4, 0x0A8C, 0x70C3, 0x068B, 0x9C53, 0xEA1B,
    0xA048, 0xD600, 0x4CD8, 0x3A90, 0x40DF, 0x3697, 0xAC4F, 0xDA07,
    0x58D1, 0x2E99, 0xB441, 0xC209, 0xB846, 0xCE0E, 0x54D6, 0x229E,
    0xC070, 0xB638, 0x2CE0, 0x5AA8, 0x20E7, 0x56AF, 0xCC77, 0xBA3F,
    0x38E9, 0x4EA1, 0xD479, 0xA231, 0xD87E, 0xAE36, 0x34EE, 0x42A6,
    0x08F5, 0x7EBD, 0xE465, 0x922D, 0xE862, 0x9E2A, 0x04F2, 0x72BA,
    0xF06C, 0x8624, 0x1CFC, 0x6AB4, 0x10FB, 0x66B3, 0xFC6B, 0x8A23,
    0xD19A, 0xA7D2, 0x3D0A, 0x4B42, 0x310D, 0x4745, 0xDD9D, 0xABD5,
    0x2903, 0x5F4B, 0xC593, 0xB3DB, 0xC994, 0xBFDC, 0x2504, 0x534C,
    0x191F, 0x6F57, 0xF58F, 0x83C7, 0xF988, 0x8FC0, 0x1518, 0x6350,
    0xE186, 0x97CE, 0x0D16, 0x7B5E, 0x0111, 0x7759, 0xED81, 0x9BC9,
    0x7927, 0x0F6F, 0x95B7, 0xE3FF, 0x99B0, 0xEFF8, 0x7520, 0x0368,
    0x81BE, 0xF7F6, 0x6D2E, 0x1B66, 0x6129, 0x1761, 0x8DB9, 0xFBF1,
    0xB1A2, 0xC7EA, 0x5D32, 0x2B7A, 0x5135, 0x277D, 0xBDA5, 0xCBED,
    0x493B, 0x3F73, 0xA5AB, 0xD3E3, 0xA9AC, 0xDFE4, 0x453C, 0x3374,
    0xB957, 0xCF1F, 0x55C7, 0x238F, 0x59C0, 0x2F88, 0xB550, 0xC318,
    0x41CE, 0x3786, 0xAD5E, 0xDB16, 0xA159, 0xD711, 0x4DC9, 0x3B81,
    0x71D2, 0x079A, 0x9D42, 0xEB0A, 0x9145, 0xE70D, 0x7DD5, 0x0B9D,
    0x894B, 0xFF03, 0x65DB, 0x1393, 0x69DC, 0x1F94, 0x854C, 0xF304,
    0x11EA, 0x67A2, 0xFD7A, 0x8B32, 0xF17D, 0x8735, 0x1DED, 0x6BA5,
    0xE973, 0x9F3B, 0x05E3, 0x73AB, 0x09E4, 0x7FAC, 0xE574, 0x933C,
    0xD96F, 0xAF27, 0x35FF, 0x43B7, 0x39F8, 0x4FB0, 0xD568, 0xA320,
    0x21F6, 0x57BE, 0xCD66, 0xBB2E, 0xC161, 0xB729, 0x2DF1, 0x5BB9,
};

Resumen

Las dos tablas CRC de FSoE son ambas tablas de búsqueda CRC-16 MSB-first para el polinomio $P(x) = $ 0x139B7. Solo difieren en la posición de byte $k$ dentro de un slice de 4 bytes:

Tabla$k$FórmulaDesplazamientosPropósito
CRC16_TABLE0$(i \cdot x^{16}) \bmod P$8Actualización CRC byte a byte ordinaria
CRC16_TABLE23$(i \cdot x^{40}) \bmod P$32Slice-by-4, byte menos significativo

El programa generador (GenTables.c) construye ambas tablas a partir únicamente del polinomio usando una sola función table_entry(i, shifts), y emite el segundo archivo fuente (Tables.c) que contiene las tablas con un comentario de cabecera autodocumentado.

Artículos relacionados

Visión general y conceptos básicos

CRC

Códigos de error y formato de datos


Echa un vistazo a artículos similares por categoría: Functional Safety C Algorithms