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.
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.
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()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)
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
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
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
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
1 2 3 4
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
I release the code into public domain.