Программная реализация CRC-алгоритма STM32 Печать
Добавил(а) microsin   

При реализации загрузчика для STM32 необходимо получить алгоритм подсчета контрольной суммы, совпадающий как на стороне микроконтроллера, так и утилиты PC, которая формирует прошивку.

Как вариант, можно воспользоваться утилитой ielftool и инструкцией [1] (см. также ссылки [2, 3, 4]). Этот метод довольно прост, но у него есть существенный недостаток - формируется прошивка, у которой размер всегда одинаковый, равный всей области памяти, отведенной под firmware приложения. А это может быть довольно большой объем данных, что может оказаться неприемлемым, если для передачи прошивки в загрузчике используется медленный канал связи.

Более интересный вариант - формировать контрольную сумму только от полезных данных firmware, и для утилиты PC воспользоваться стандартным алгоритмом CRC32 [5]. Конечно, тот же самый алгоритм можно реализовать программно и на стороне STM32.

Алгоритм функции crc32_formula_normal [5] одинаково работает как на стороне PC, так и на стороне STM32. Он вычисляет CRC32 программно, от любого количества байт, даже нечетного.

static uint8_t reverse (uint8_t val8)
{
   uint8_t result = 0;
   uint8_t maskSRC = 0x01;
   uint8_t maskDST = 0x80;
 
   for (int i=0; i < 8; i++)
   {
      if (val8 & maskSRC)
         result |= maskDST;
      maskSRC << = 1;
      maskDST >> = 1;
   }
   
   return result;
}
 
static uint32_t reflect32 (uint32_t val32)
{
   uint32_t result = 0;
   uint32_t maskSRC = 0x00000001;
   uint32_t maskDST = 0x80000000;
 
   for (int i=0; i < 32; i++)
   {
      if (val32 & maskSRC)
         result |= maskDST;
      maskSRC << = 1;
      maskDST >> = 1;
   }
   
   return result;
}
 
uint32_t crc32_formula_normal( size_t len,
                               const void *data )
{
#define POLY 0x04C11DB7
   const unsigned char *buffer = (const unsigned char*) data;
   uint32_t crc = -1;
 
   while( len-- )
   {
      crc = crc ^ ((uint32_t)reverse(*buffer++) << 24);
      for( int bit = 0; bit < 8; bit++ )
      {
         if( crc & (1L << 31)) crc = (crc << 1) ^ POLY;
         else                  crc = (crc << 1);
      }
   }
   return reflect32( ~crc );
}

Достоинства показанного выше программного решения - результат работы алгоритма совпадает со стандартным (по тестовой строке "123456789" получается 0xCBF43926), реализация занимает мало места в памяти. Однако по понятным причинам работает эта реализация медленно, что может быть иногда проблемой на стороне STM32.

[Использование аппаратного блока вычисления контрольной суммы STM32]

Конечно, на стороне PC как вычислительных ресурсов, так и ресурсов памяти предостаточно, так что нет никаких проблем реализовать как медленный программный, так и табличный алгоритм вычисления CRC32 (см. 5). Но нас стороне микроконтроллера STM32 такой роскоши нет, зато есть аппаратный блок вычисления контрольной суммы [6, 7]. Поэтому возникает вопрос - можно ли как-то на стороне PC получить алгоритм, результат работы которого будет совпадать с алгоритмом PC? Оказывается, это возможно, но необходимо в реализации PC учесть следующее:

1. Алгоритм [5] подразумевает вычисление CRC32 по байтам, т. е. можно вычислить контрольную сумму от любого количества байт, даже нечетного. Аппаратный блок CRC32 не у всех моделей микроконтроллеров STM32 дает такую возможность. Например, у STM32F407 вычислять CRC32 можно только словами, т. е. блоками по 4 байта. Поэтому на стороне PC и на стороне STM32, если размер области данных, по которым вычисляется CRC, не делится нацело на 4, то надо дополнить эту область необходимым количеством данных с заранее известным значением.

2. На стороне PC необходимо подавать байты в алгоритм вычисления CRC32 в порядке, соответствующем порядку байт в слове (uint32_t) STM32.

Ниже во врезке показан код для PC, который вычисляет контрольную сумму от данных так же (т. е. дает тот же конечный результат), как это делает аппаратный блок CRC32 микроконтроллера STM32Fxx. Функция reorder4 решает вышеупомянутые проблемы 1 и 2. Она дополняет размер вычисляемых данных до значения, нацело делящееся на 4, значение дополняемых байт выбрано 0xFF (значение байт "чистой" области FLASH), и одновременно упорядочивает порции данных по 4 байта в необходимой (обратной) последовательности.

Алгоритм функции crc32_formula_normal_STM32 предназначен для работы на стороне PC. Он вычисляет CRC32 от любого количества байт, даже нечетного. Если это количество байт не делится нацело на 4, то оно дополняется байтами 0xFF. CRC32 вычисляется порциями по 4 байта, перестановку байт в четверках и дополнение выполняет функция reorder4.

////////////////////////////////////////////////////////////////////
// Вычисление контрольной суммы на стороне PC.
static uint8_t *reorder4 (uint8_t *src, uint32_t len)
{
   static uint8_t dst[4];
   uint8_t appendlen, idx;
 
   len = (len % 4)+4;
   appendlen = (len % 4) ? 4-(len % 4) : 0;
   idx = 0;
   while(appendlen--)
   {
      dst[idx] = 0xFF;
      idx++;
   }
   while(len--)
   {
      dst[idx] = src[3-idx];
      idx++;
   }
   return dst;
}
 
uint32_t crc32_formula_normal_STM32( size_t len,
                                     void *data )
{
#define POLY 0x04C11DB7
   uint8_t *buffer = (uint8_t*)data;
   uint32_t crc = -1;
   uint32_t portion;
   uint8_t *reordered;
 
   while( len )
   {
      portion = len < 4 ? len : 4;
      reordered = reorder4(buffer, portion);
      for (uint8_t i=0; i < 4; i++)
      {
         crc = crc ^ ((uint32_t)reordered[i] << 24);
         for( int bit = 0; bit < 8; bit++ )
         {
            if( crc & (1L << 31)) crc = (crc << 1) ^ POLY;
            else                  crc = (crc << 1);
         }
      }
      buffer += portion;
      len -= portion;
   }
   return crc;
} 

Пример вычисления контрольной суммы на стороне PC по стандартной тестовой строке:

char ASCII[10] = {"123456789"};
 
printf ("Calc CRC32 software...\r\n");
uint32_t crc32Result = crc32_formula_normal_STM32(9, ASCII);
printf ("CRC32=%08X\r\n", crc32Result);

Будет выведено:

Calc CRC32 software...
CRC32=D9020D98

На стороне STM32 получается такой же результат. Обратите внимание, что в качестве входных данных передается массив размером 12 байт, потому что 12 нацело делится на 4 (в HAL_CRC_Calculate передается 3 слова). В конце полезных данных добавлено 3 байта 0xFF, это такие же данные, как подставляет в алгоритм на стороне PC функция reorder4 из первой врезки "Оригинальный алгоритм CRC32 по полиному 0x04C11DB7".

////////////////////////////////////////////////////////////////////
// Вычисление контрольной суммы на стороне STM32.
char ASCII[12] = {'1',
                  '2',
                  '3',
                  '4',
                  '5',
                  '6',
                  '7',
                  '8',
                  '9',
                  0xFF,
                  0xFF,
                  0xFF
};
 
printf ("Calc CRC32 hardware...\r\n");
MX_CRC_Init();uint32_t uwCRCValue = HAL_CRC_Calculate(&hcrc, (uint32_t *)ASCII, 3);
MX_CRC_DeInit();
printf ("CRC32=%08X\r\n", uwCRCValue);

Будет выведено:

Calc CRC32 hardware...
CRC32=D9020D98

[Ссылки]

1. CRC прошивки средствами IAR и STM32 site:tqfp.org.
2. Checksum calculation with IAR ILINK Linker and IELFTOOL site:iar.com.
3. Calculating CRC32 with IAR ELF Tool the same way as STM32 hardware site:iar.com.
4. IELFTOOL Checksum - The basic actions site:iar.com.
5. Демистификация CRC32.
6. STM32F4xx: блок вычисления CRC.
7. STM32: использование аппаратного блока CRC.
8. STM32 CRC32 software implementation code site:programmersought.com.
9210427122559SD-card-boot.zip - загрузчик akospasztor / stm32-bootloader, портированный на STM32F407, проект C# утилиты PC, которая программно подсчитывает контрольную сумму так же как это делает аппаратура STM32.