4

I'm working on a communication link between a computer running Linux and a STM32F0. I want to use some kind of error detection for my packets and since the STM32F0 has CRC32 hw and I have zlib with CRC32 on Linux I thought it would be a good idea to use CRC32 for my project. The problem is that I won't get the same CRC value for the same data on the different platforms.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>

int
main(void)
{
  uint8_t byte0 = 0x00;
  uint32_t crc0 = crc32(0L, Z_NULL, 0);
  crc0 = crc32(crc0, &byte0, 1);
  printf("CRC32 value of %" PRIu8 " is: %08" PRIx32 "\n", byte0, crc0);
}

outputs CRC32 value of 0 is: d202ef8d which matches the result on several online calculators.

It seems though that whatever settings I use on the STM32 I can't get the same CRC. I have found a flowchart on how the CRC hw calculates its value in an application note from ST but I can't figure out how it's done in zlib.

Does anyone know if they are compatible?

[edit 1] They both uses the same init value and polynomial.

[edit 2] The STM32 code is relatively uninteresging since it's using the hw.

...
/* Default values are used for init value and polynomial, see edit 1 */
CRC->CR |= CRC_CR_RESET;
CRC->DR = (uint8_t)0x00;
uint32_t crc = CRC->DR;
...
evading
  • 3,032
  • 6
  • 37
  • 57
  • If STM32 version gives you the incorrect result, you should probably show the relevant code with the results. – user694733 Jan 21 '15 at 10:23
  • Doc you linked said that start value on that model should be programmable, but your code doesn't initialize it. Also, you should also show the result you get from that. – user694733 Jan 21 '15 at 10:39

6 Answers6

10

The CRC32 implementation on STM32Fx seems to be not the standard CRC32 implementation you find on many online CRC calculators and the one used in zip.

STM32 implements CRC32-MPEG2 which uses big endian and no final flip mask compared to the zip CRC32 which uses little endian and a final flip mask.

I found this online calculator which supports CRC32-MPEG2.

If you are more interested on other CRC algorithms and their implementation, look at this link.

PS: The HAL driver from STM supports input in byte, half word and word formats and they seem to work fine for STM32F0x in v1.3.1

Ayyala
  • 159
  • 4
  • 7
  • 2
    FYI: You can configure STM32F0 to give you CRC32 as per zip using the `CRC_ReverseInputDataSelect(CRC_ReverseInputData_32bits);` `CRC_ReverseOutputDataCmd(ENABLE);` in the `stdperiph` library - I haven't looked at the newer `HAL` library. You'll just have to invert the result you read from the hardware. – Greg Jul 31 '16 at 09:36
6

From the documentation, it appears that your STM32 code is not just uninteresting — it is rather incomplete. From the documentation, in order to use the CRC hardware you need to:

  1. Enable the CRC peripheral clock via the RCC peripheral.
  2. Set the CRC Data Register to the initial CRC value by configuring the Initial CRC value register (CRC_INIT).(a)
  3. Set the I/O reverse bit order through the REV_IN[1:0] and REV_OUT bits respectively in CRC Control register (CRC_CR).(a)
  4. Set the polynomial size and coefficients through the POLYSIZE[1:0] bits in CRC Control register (CRC_CR) and CRC Polynomial register (CRC_POL) respectively.(b)
  5. Reset the CRC peripheral through the Reset bit in CRC Control register (CRC_CR).
  6. Set the data to the CRC Data register.
  7. Read the content of the CRC Data register.
  8. Disable the CRC peripheral clock.

Note in particular steps 2, 3, and 4, which define the CRC being computed. They say that their example has rev_in and rev_out false, but for the zlib crc, they need to be true. Depending on the way the hardware is implemented, the polynomial will likely need to reversed as well (0xedb88320UL). The initial CRC needs to be 0xffffffff, and the final CRC inverted to match the zlib crc.

Mark Adler
  • 101,978
  • 13
  • 118
  • 158
  • 3
    To think I would get an answer from the "God Father" of zlib himself, fantastic! The CRC_INIT and Polynomial are actually the same as zlib by default. It was the rev_in, rev_out and final inversion of the result that got me there. Thanks! – evading Jan 22 '15 at 07:08
  • Unfortunately this doesn't seem to work on the STM32F4xx. It has a data register and a reset bit. No other configuration is possible. – curator23 Feb 07 '19 at 11:06
  • @curator23 Oh well. It does however use the correct polynomial, but without reversing the order in which the bits are processed. There might still be a benefit to using the hardware CRC over a software CRC if you reverse the bits in the bytes you feed to the CRC, and then reverse the bits in the resulting CRC. – Mark Adler Feb 07 '19 at 18:53
  • @MarkAdler Yes, found the 'reverse bits' instruction rbit and its gcc macro __RBIT in core_cmInst.h. Wouldn't work with DMA, but that seems an unlikely scenario. I guess you could pre-reverse the bits and then initiate DMA if you really wanted :) – curator23 Feb 14 '19 at 16:58
1

I haven't tested this, but I suspect that you're not, in fact, doing an 8-bit write on the STM32.

Instead you're probably doing a write to the full width of the register (32 bits), which of course means you're computing the CRC32 of more bytes than you intended.

Disassemble the generated code to analyze the exact store instruction that is being used.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • Yes, as a matter of fact it is generating a `str` instruction. Do you know how I force arm gcc to generate a `strb` instruction instead? – evading Jan 21 '15 at 14:57
  • First, make sure (by reading the CPU docs) that it really *can* update the CRC by just 8 bits of data. I looked in a similar chip's docs, and it says the CRC32 module is 32-bit only. So, the reasonable thing to do then is to change your zlib test to update by four 0-bytes and compare that. – unwind Jan 21 '15 at 15:05
  • 1
    The 8-bit issue (`strb`) will be because `CRC->DR` is defined as `volatile uint32_t*`, thus all accesses are word oriented (independent of the cast you did) try casting to a `volatile uint8_t*` – Greg Jul 30 '16 at 10:35
1

I had similar issues implementing a CRC on an STM32 CRC module where the final checksum was not matching. I was able to fix this by looking at the example code in stm32f30x_crc.c that is provided with STMCube. In the source it has a function for 8-bit CRC that used the following code

(uint8_t)(CRC_BASE) = (uint8_t) CRC_Data;

to write to the DR register. As stated previously, the line CRC->DR is defined as volatile uint32_t which will access the whole register. It would have been helpful if ST had been more explicit about accessing the DR register rather than simply saying it supports 8-bit types.

James Yau
  • 31
  • 2
0

From my experience, you can not compare the CRC32 code between the STM32 CRC unit output with the online CRC32 calculator.

Please find my CRC32 calculator for STM32 in this link.

This function has been used in my project, and proved to be correct.

Community
  • 1
  • 1
Enix
  • 4,415
  • 1
  • 24
  • 37
0

For reference, getting compliant crc32 using the Low Level API:

void crc_init(void) {
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_CRC);

    LL_CRC_SetPolynomialCoef(CRC, LL_CRC_DEFAULT_CRC32_POLY);
    LL_CRC_SetPolynomialSize(CRC, LL_CRC_POLYLENGTH_32B);
    LL_CRC_SetInitialData(CRC, LL_CRC_DEFAULT_CRC_INITVALUE);
    LL_CRC_SetInputDataReverseMode(CRC, LL_CRC_INDATA_REVERSE_WORD);
    LL_CRC_SetOutputDataReverseMode(CRC, LL_CRC_OUTDATA_REVERSE_BIT);
}

uint32_t crc32(const char* const buf, const uint32_t len) {
    uint32_t data;
    uint32_t index;

    LL_CRC_ResetCRCCalculationUnit(CRC);

    /* Compute the CRC of Data Buffer array*/
    for (index = 0; index < (len / 4); index++) {
        data = (uint32_t)((buf[4 * index + 3] << 24) |
                          (buf[4 * index + 2] << 16) |
                          (buf[4 * index + 1] << 8) | buf[4 * index]);
        LL_CRC_FeedData32(CRC, data);
    }

    /* Last bytes specific handling */
    if ((len % 4) != 0) {
        if (len % 4 == 1) {
            LL_CRC_FeedData8(CRC, buf[4 * index]);
        }
        if (len % 4 == 2) {
            LL_CRC_FeedData16(CRC, (uint16_t)((buf[4 * index + 1] << 8) |
                                              buf[4 * index]));
        }
        if (len % 4 == 3) {
            LL_CRC_FeedData16(CRC, (uint16_t)((buf[4 * index + 1] << 8) |
                                              buf[4 * index]));
            LL_CRC_FeedData8(CRC, buf[4 * index + 2]);
        }
    }

    return LL_CRC_ReadData32(CRC) ^ 0xffffffff;
}

Tweaked from the example here

The LL_CRC_ResetCRCCalculationUnit() re-initializes the crc unit so that repeated calls to this function return the expected value, otherwise the initial value will be whatever the result of the previous crc was, instead of 0xFFFFFFFF

You probably don't need the lines setting the polynomial to the default value because they should already be initialized to these values from reset, but I've left them in for posterity.

Peter Frost
  • 351
  • 3
  • 6