CRC32: на STM32 как на компе или на компе как на STM32.

(во вложении исходники srecord crc32stm32)

Все знают, что в STM32F1xx, STM32F2xx, STM32F4xx есть аппаратный блок CRC32 с полиномом 0x04C11DB7.
И он, в общем-то, работает. Но только контрольная сумма почему-то не совпадает с таковой, рассчитанной софтварно.
В гугле обычно 2 типа вопросов:

  1. Как хардварно посчитать на STM32 побайтовую CRC
  2. Как посчитать софтово CRC так, чтоб она совпала с хардовой на STM32

Причём, на первый вопрос ответ везде отрицательный. Так ли это? Попробуем разобраться.

Софтварно CRC32 обычно считается побайтово и (как в эзернете) — младшим битом вперёд, сдвиг LSFR — вправо, в сторону младшего бита, поэтому используется реверсированный полином 0xEDB88320.
Регистр данных же в CRC блоке STM32 — 32х-битный и сдвигается в сторону старшего бита.
Чтоб понять, почему так, немного иллюстрации:

  • Во-первых, CRC — побитовая функция. параллельный подсчёт CRC — плюшки из следствий математики двоичных полиномов.
  • порядок загрузки бит в LSFR не должен нарушаться в зависимости от разрядности и остроконечности архитектуры

Смотрим на рисунок: все биты поступают по порядку, цветами я пометил биты, которые соответствуют друг другу для прямого и зеркального полиномов, нумерация байтов совпадает со смещением в памяти.
Т.е., CRC32 на STM32 можно посчитать так же, как и принято в ethernet. Для этого надо реверсировать входные слова и реверсировать в итоге контрольную сумму. Это работает только для длины, кратной 4.
Сперва совтварная реализация.
Инициализируем таблицы остатков для быстрого вычисления CRC:

static uint32_t crc32_table[256];
static uint32_t crc32r_table[256];

#define CRC32_POLY   0x04C11DB7
#define CRC32_POLY_R 0xEDB88320

static void crc32_init(void)
{
        int i, j;
        uint32_t c, cr;
        for (i = 0; i < 256; ++i) {
                cr = i;
                c = i << 24;
                for (j = 8; j > 0; --j) {
                        c = c & 0x80000000 ? (c << 1) ^ CRC32_POLY : (c << 1);
                        cr = cr & 0x00000001 ? (cr >> 1) ^ CRC32_POLY_R : (cr >> 1);
                        }
                crc32_table[i] = c;
                crc32r_table[i] = cr;
                //printf("f[%02X]=%08X\t", i, crc32_table[i]);
                //printf("r[%02X]=%08X\t", i, crc32r_table[i]);
        }
        //printf("\n");
}



Побайтовое вычисление «нормальной CRC»:

uint32_t crc32_byte(uint32_t init_crc, uint8_t *buf, int len)
{
        uint32_t v;
        uint32_t crc;
        crc = ~init_crc;
        while(len > 0) {
                v = *buf++;
                crc = ( crc >> 8 ) ^ crc32r_table[( crc ^ (v ) ) & 0xff];
                len --;
        }
        return ~crc;
}



Вычисление CRC как на CRC блоке STM32:

uint32_t crc32_stm32(uint32_t init_crc, uint32_t *buf, int len)
{
        uint32_t v;
        uint32_t crc;
        crc = ~init_crc;
        while(len >= 4) {
                v = htonl(*buf++);
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v ) )];
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 8) )];
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 16) )];
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 24) )];
                len -= 4;
        }
        if(len) {
                switch(len) {
                        case 1: v = 0xFF000000 & htonl(*buf++); break;
                        case 2: v = 0xFFFF0000 & htonl(*buf++); break;
                        case 3: v = 0xFFFFFF00 & htonl(*buf++); break;
                }
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v ) )];
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 8) )];
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 16) )];
                crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 24) )];
        }
        return ~crc;
}


Тут я применил htonl чтоб байты в слове были в определённом порядке независимо от LE/BE: сначала в LSFR заправляется байт, который лежит в памяти по смещению 3 (как на рисунке). Ещё, остаток сообщения, не влезающий в 4х-байтовое слово дополняется нулями справа и CRC досчитвается дальше.
Можно писать конструкцию типа такой (вычислять CRC кусками):

printf("crc32_byte   = %08X\n", crc32_byte(crc32_byte(0, "12345", 5), "6789", 4));
printf("crc32_stm32   = %08X\n", crc32_stm32(crc32_stm32(0, "1234", 4), "56789", 5));



Вот то, что получается:
crc32_byte(«123456789») = CBF43926
crc32_stm32(«123456789») = 500E6FA8
crc32_byte(«12345678») = 9AE0DAAF
crc32_stm32(«12345678») = 0103AB06

Теперь код для STM32:
Сперва чисто хардварная CRC (ей соответствует софтовая crc32_stm32):

    uint32_t crc32_native(char *bfr, int len, int clear) {
        int l;
        uint32_t *p, x, crc;
        l = len / 4;
        p = (uint32_t*)bfr;
        x = p[l];
        if(clear) CRC_ResetDR();
        while(l--) {
                crc = CRC_CalcCRC(*p++);
        }
        switch(len & 3) {
                case 1: crc = CRC_CalcCRC(x & 0x000000FF); break;
                case 2: crc = CRC_CalcCRC(x & 0x0000FFFF); break;
                case 3: crc = CRC_CalcCRC(x & 0x00FFFFFF); break;
        }
        return 0xFFFFFFFF ^ crc;
}



Далее сделаем «как на софте», или «как в ethernet». Благо, в ARM есть инструкция для реверсирования бит.
Но это не всё. Ведь если пораскинуть мозгами, то можно добавить вкусную плюшку — сосчитать-таки побайтовую CRC на аппаратном блоке.
Нужно просто вычислить полином-остаток от оставшегося куска и добавить его к уже посчитанной пословно CRC. Остаток — это по сути та же CRC, но с начальным состоянием LSFR=0 (см инициализацию таблицы остатков). Но вот беда — CRC_ResetDR может установить регистр CRC только в 0xFFFFFFFF. Слава яйцам, что нам надо именно 0, а не что-то ещё. Одно из свойств CRC состоит в том, что если к сообщению приписать его CRC, то CRC нового сообщения будет равна 0. Другими словами, если мы подадим в регистр CRC то, что мы из него считали, то в результате получится 0. Теперь осталось заправить в регистр кусочек из одного, двух или трёх оставшихся байт — et voila, забираем наш полином-остаток и добавляем его к CRC.
Ниже код:
 

uint32_t reverse_32(uint32_t data)
{
        asm("rbit r0,r0");
        return data;
};

uint32_t crc32_ether(char *buf, int len, int clear)
{
        uint32_t *p = (uint32_t*) buf;
        uint32_t crc, crc_reg;
        if(clear) CRC_ResetDR();
        if(len >=4) {
                while(len >= 4) {
                        crc_reg = CRC_CalcCRC(reverse_32(*p++));
                        len -= 4;
                }
        } else {
                crc = 0xFFFFFFFF;
                crc_reg = CRC_CalcCRC(0xEBABAB);
        }
        crc = reverse_32(crc_reg);
        if(len) {
                CRC_CalcCRC(crc_reg);
                switch(len) {
                        case 1:
                        crc_reg = CRC_CalcCRC(reverse_32((*p & 0xFF) ^ crc) >> 24);
                        crc = ( crc >> 8 ) ^ reverse_32(crc_reg);
                        break;
                        case 2:
                        crc_reg = CRC_CalcCRC(reverse_32((*p & 0xFFFF) ^ crc) >> 16);
                        crc = ( crc >> 16 ) ^ reverse_32(crc_reg);
                        break;
                        case 3:
                        crc_reg = CRC_CalcCRC(reverse_32((*p & 0xFFFFFF) ^ crc) >> 8);
                        crc = ( crc >> 24 ) ^ reverse_32(crc_reg);
                        break;
                }
        }
        return ~crc;
}



Вот, как-то так, думаю, многим пригодится.

P.S. бонус:

uint8_t pkt_alt[] = {
        0x00, 0x10, 0xA4, 0x7B, 0xEA, 0x80, 0x00, 0x12,
        0x34, 0x56, 0x78, 0x90, 0x08, 0x00, 0x45, 0x00,
        0x00, 0x2E, 0xB3, 0xFE, 0x00, 0x00, 0x80, 0x11,
        0x05, 0x40, 0xC0, 0xA8, 0x00, 0x2C, 0xC0, 0xA8,
        0x00, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, 0x1A,
        0x2D, 0xE8, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
        0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
        0x0E, 0x0F, 0x10, 0x11, 0xB3, 0x31, 0x88, 0x1B
};

uint8_t pkt_alt_d[] = {
        0x00, 0xC0, 0x02, 0x37, 0x57, 0x28, 0x00, 0x10,
        0xA4, 0x7B, 0xEA, 0x80, 0x08, 0x00, 0x45, 0x00,
        0x00, 0x3C, 0x02, 0x24, 0x00, 0x00, 0x80, 0x01,
        0xB7, 0x47, 0xC0, 0xA8, 0x00, 0x04, 0xC0, 0xA8,
        0x00, 0x01, 0x08, 0x00, 0x42, 0x5C, 0x02, 0x00,
        0x09, 0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
        0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
        0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
        0x77, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
        0x68, 0x69, 0x62, 0x31, 0xC5, 0x4E
};

uint8_t pkt_xil[] = {
        0x00, 0x0A, 0xE6, 0xF0, 0x05, 0xA3, 0x00, 0x12,
        0x34, 0x56, 0x78, 0x90, 0x08, 0x00, 0x45, 0x00,
        0x00, 0x30, 0xB3, 0xFE, 0x00, 0x00, 0x80, 0x11,
        0x72, 0xBA, 0x0A, 0x00, 0x00, 0x03, 0x0A, 0x00,
        0x00, 0x02, 0x04, 0x00, 0x04, 0x00, 0x00, 0x1C,
        0x89, 0x4D, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
        0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
        0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
        0x7A, 0xD5, 0x6B, 0xB3
};


это, если кому надо, реальные ethernet-фреймы с CRC32 FCS

Добавить комментарий

Обратная связь

Интересуют вопросы реализации алгоритмов, программирования, выбора электроники и прочая информация, постараюсь осветить в отдельных статьях

пишите мне на netdm@mail.ru