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.
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 snipped 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; } |
All code is public domain.