M0AGX / LB9MG

Amateur radio and embedded systems

Matching STM32 hardware CRC with standard CRC-32

Hardware CRC generators come handy in embedded systems when data or code have to be validated. For example: storing settings in flash or EEPROM, receiving new firmware via a bootloader. Software implementations are either slow (when calculating the CRC directly) or need quite some memory for lookup tables. There are many CRC standards. The checksum does not necessarily have to be standards-compliant when used for internal data storage because the data will never leave the device in raw binary. However, when the data has to be exchaned with an external system it helps greatly when off the shelf libraries can be used.

I tried using the CRC peripheral available in STM32L011 and failed miserably so I had to come up with a more systematic approach to get a proper CRC-32 that is compatible with zlib, Python etc.

The hardware

STM32L011 CRC peripheral has the following registers:

  • data register - used to write the data to be checksummed and read out the result
  • independent data register - not used
  • control register - has all the settings, this is the tough nut to crack
  • initial CRC value - speaks for itself
  • polynomial - also speaks for itself, the default value is the same as Wikipedia mentions for CRC-32 HDLC, Ethernet, Bzip2 etc.

STM32 CRC CR register

The variables

CRC-32 polynomial is 0x04C11DB7 (standard), polynomial size is obvious (32-bits) and the initial state is 0xFFFFFFFF. This leaves the following variables:

  • CRC-32 operates on 4-byte words so a crc32([0]) is a different thing than crc32([0,0,0,0]) in Python syntax
  • the polynomial itself
  • input bit order reversal
  • output bit order reversal
  • output result inversion (done in software)

Reference results

The first thing is to have a reference you can trust. I picked Python and zlib. A word full of zero bits is the easiest because input bit reversal does not matter.

1
2
3
4
5
6
7
>>> import binascii
>>> import zlib
>>> print("%08X" % binascii.crc32(bytes([0,0,0,0])))
2144DF1C
>>> print("%08X" % zlib.crc32(bytes([0,0,0,0])))
2144DF1C
>>>

Python binascii and zlib implementations seem identical and the result for a word of zeros is 2144DF1C.

First attempt - all zeros

The CRC peripheral is very easy to use. Basically: enable the clock, set up the polynomial and reversals, keep writing the data and in the end read the result. I wrote this short snippet that tries both output bit reversals and output result inversion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
RCC->AHBENR |= RCC_AHBENR_CRCEN_Msk; //enable clock to CRC peripheral

uint32_t x = 0; //test word - all zeros

CRC->CR = CRC_CR_RESET_Msk; //reset the CRC hardware, all other bits zeros
CRC->DR = x; //load test word
SEGGER_RTT_printf(0, "%08X %08X\n", CRC->DR, ~CRC->DR); //print output and inverted output

CRC->CR = CRC_CR_RESET_Msk | CRC_CR_REV_OUT_Msk; //reset the CRC hardware and reverse output bits
CRC->DR = x; //load test word
SEGGER_RTT_printf(0, "%08X %08X\n", CRC->DR, ~CRC->DR); //print output and inverted output

The result:

1
2
C704DD7B 38FB2284
DEBB20E3 2144DF1C

How to interpret the output (we are looking for the combination that returns 2144DF1C):

  • no output reversal, no result inversion - bad
  • no output reversal, result inversion - bad
  • output reversal, no result inversion - bad
  • output reversal, result inversion - GOOD!

Conclusion: output reversal has to be enabled and the result must be inverted.

Second attempt - test number

Having the right setting for an all-zero word the next step is to try any other number. I picked 0x00010203 (watch out for endianness!). Python reference:

1
2
3
4
>>> print("%08X" % binascii.crc32(bytes([0x03, 0x02, 0x01, 0x00])))
296E95DD
>>> print("%08X" % zlib.crc32(bytes([0x03, 0x02, 0x01, 0x00])))
296E95DD

We will be looking for 296E95DD.

This piece of code tests various input reversals (none, byte, half-word, word). The question of ouput inversion is already settled but I keep printing both options just in case.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
RCC->AHBENR |= RCC_AHBENR_CRCEN_Msk;

uint32_t x = 0x00010203;

CRC->CR = CRC_CR_RESET_Msk | CRC_CR_REV_OUT_Msk;
CRC->DR = x;
SEGGER_RTT_printf(0, "%08X %08X\n", CRC->DR, ~CRC->DR);

CRC->CR = CRC_CR_RESET_Msk | CRC_CR_REV_OUT_Msk | CRC_CR_REV_IN_0;
CRC->DR = x;
SEGGER_RTT_printf(0, "%08X %08X\n", CRC->DR, ~CRC->DR);

CRC->CR = CRC_CR_RESET_Msk | CRC_CR_REV_OUT_Msk | CRC_CR_REV_IN_1;
CRC->DR = x;
SEGGER_RTT_printf(0, "%08X %08X\n", CRC->DR, ~CRC->DR);

CRC->CR = CRC_CR_RESET_Msk | CRC_CR_REV_OUT_Msk | CRC_CR_REV_IN_0 | CRC_CR_REV_IN_1;
CRC->DR = x;
SEGGER_RTT_printf(0, "%08X %08X\n", CRC->DR, ~CRC->DR);

The result:

1
2
3
4
5493B6D6 AB6C4929
744679EC 8BB98613
A3247569 5CDB8A96
D6916A22 296E95DD

How to interpret the output (we are looking for the combination that returns 296E95DD):

  • output reversal, no input reversal, no result inversion - bad
  • output reversal, no input reversal, result inversion - bad
  • output reversal, input reversal by byte, no result inversion - bad
  • output reversal, input reversal by byte, result inversion - bad
  • output reversal, input reversal by half-word, no result inversion - bad
  • output reversal, input reversal by half-word, result inversion - bad
  • output reversal, input reversal by full word, no result inversion - bad
  • output reversal, input reversal by full word, result inversion - GOOD!

Conclusion: output reversal has to be enabled, input reversal must be enabled and done by full word, the result must be inverted. :)

CRC-32 compatible function

The examples above operate on a single word but this function has been successfully used with a larger block of data in a bootloader.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdint.h>
#include <stm32l011xx.h>

//CRC-32 compatible function, uses reset value of polynomial in CRC->POL (0x04C11DB7)
uint32_t crc32(const uint32_t *data, uint32_t length_bytes){
    RCC->AHBENR |= RCC_AHBENR_CRCEN_Msk;
    CRC->CR = CRC_CR_RESET_Msk | CRC_CR_REV_OUT_Msk | CRC_CR_REV_IN_0 | CRC_CR_REV_IN_1; //reset the CRC hardware, 4 byte mode, reversal

    for (uint32_t i = 0; i < (length_bytes / sizeof(uint32_t)); i++){
        CRC->DR = *data;
        data++;
    }
    return ~CRC->DR;
}

I release the code into public domain.