CRC32: на STM32 как на компе или на компе как на STM32.
(во вложении исходники srecord crc32stm32)
Все знают, что в STM32F1xx, STM32F2xx, STM32F4xx есть аппаратный блок CRC32 с полиномом 0x04C11DB7.
И он, в общем-то, работает. Но только контрольная сумма почему-то не совпадает с таковой, рассчитанной софтварно.
В гугле обычно 2 типа вопросов:
- Как хардварно посчитать на STM32 побайтовую CRC
- Как посчитать софтово 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
Добавить комментарий