STM32F0和zlib之间的CRC32匹配

4
我正在处理一台运行Linux的计算机和STM32F0之间的通信链路。我想在我的数据包中使用某种错误检测方法,由于STM32F0具有CRC32硬件,而我在Linux上有带有CRC32的zlib库,所以我认为使用CRC32对于我的项目是一个好主意。问题是,在不同平台上相同数据的CRC值将不同。
#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);
}

输出结果为CRC32 value of 0 is: d202ef8d,与多个在线计算器上的结果相同。

无论我在STM32上使用什么设置,似乎都无法得到相同的CRC值。我在ST的应用笔记中找到了一个流程图,说明了CRC硬件如何计算其值,但我不知道zlib是如何实现的。

有人知道它们是否兼容吗?

[编辑1]它们都使用相同的初始值和多项式。

[编辑2]由于它使用硬件,因此STM32代码相对不太重要。

...
/* 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;
...

如果STM32版本给出了错误的结果,您应该展示相关代码和结果。 - user694733
你链接的文档说该模型的起始值应该是可编程的,但你的代码没有初始化它。此外,你还应该展示一下得到的结果。 - user694733
6个回答

10

在STM32Fx上实现的CRC32似乎不是在许多在线CRC计算器中找到的标准CRC32实现,也不是zip中使用的那种。

STM32实现了CRC32-MPEG2,它使用大端字节序和没有最终翻转掩码,而zip CRC32使用小端字节序和最终翻转掩码。

我发现这个在线计算器支持CRC32-MPEG2。

如果您对其他CRC算法及其实现更感兴趣,请查看此链接

PS:来自STM的HAL驱动程序支持以字节、半字和字格式输入,并且在v1.3.1中似乎适用于STM32F0x。


2
FYI:您可以使用stdperiph库将STM32F0配置为按zip格式提供CRC32,方法是使用CRC_ReverseInputDataSelect(CRC_ReverseInputData_32bits); CRC_ReverseOutputDataCmd(ENABLE); - 我还没有查看过更新的HAL库。您只需要反转从硬件读取的结果即可。 - Greg

6

根据文档显示,您的STM32代码不仅仅是无趣的——而是相当不完整的。要使用CRC硬件,需要执行以下操作:

  1. 通过RCC外设启用CRC外设时钟。
  2. 通过配置初始CRC值寄存器(CRC_INIT)将CRC数据寄存器设置为初始CRC值。(a)
  3. 在CRC控制寄存器(CRC_CR)中分别通过REV_IN[1:0]和REV_OUT位设置I/O反转位序。(a)
  4. 通过CRC控制寄存器(CRC_CR)的POLYSIZE[1:0]位和CRC多项式寄存器(CRC_POL)分别设置多项式大小和系数。(b)
  5. 通过CRC控制寄存器(CRC_CR)的Reset位重置CRC外设。
  6. 将数据设置到CRC数据寄存器。
  7. 读取CRC数据寄存器的内容。
  8. 禁用CRC外设时钟。

特别注意步骤2、3和4,它们定义了正在计算的CRC。他们说他们的示例中rev_in和rev_out为false,但对于zlib crc,它们需要为true。根据硬件实现的方式,多项式可能也需要反转(0xedb88320UL)。初始CRC需要为0xffffffff,最终CRC需要反转以匹配zlib crc。


3
想不到我会从zlib的“教父”那里得到答案,太棒了!CRC_INIT和Polynomial在默认情况下实际上与zlib相同。是rev_in、rev_out和最终结果的反转帮助了我。谢谢! - evading
不幸的是,这在STM32F4xx上似乎无法工作。它有一个数据寄存器和一个复位位。没有其他配置可用。 - curator23
@curator23 哦,好吧。它确实使用了正确的多项式,但没有颠倒处理位的顺序。如果您将字节中的位反转,然后将结果CRC中的位反转,使用硬件CRC仍可能比使用软件CRC更有益。 - Mark Adler
@MarkAdler 是的,在 core_cmInst.h 中找到了“反转位”指令 rbit 及其 gcc 宏 __RBIT。虽然在 DMA 中不起作用,但这似乎是不太可能的情况。我想如果你真的想要的话,你可以预先反转位,然后启动 DMA :) - curator23

1
我没有测试过这个,但我怀疑你实际上并没有在STM32上进行8位写入。
相反,你可能正在对寄存器的全部宽度(32位)进行写入,这当然意味着你正在计算比预期更多字节的CRC32。
反汇编生成的代码以分析正在使用的精确存储指令。

事实上,它正在生成一个 str 指令。你知道我如何强制 arm gcc 生成一个 strb 指令吗? - evading
首先,请确保(通过阅读CPU文档)它确实可以仅使用8位数据更新CRC。我查看了类似芯片的文档,它说CRC32模块仅支持32位。因此,合理的做法是将您的zlib测试更改为通过四个0字节进行更新并进行比较。 - unwind
1
8位问题(strb)是因为CRC->DR被定义为volatile uint32_t*,因此所有访问都是以字为单位的(与您所做的强制转换无关)。尝试将其转换为volatile uint8_t* - Greg

1
我曾在STM32 CRC模块上实现CRC时遇到类似问题,最终的校验和不匹配。通过查看STMCube提供的stm32f30x_crc.c示例代码,我得以解决这个问题。源码中有一个8位CRC函数,使用了以下代码:(uint8_t)(CRC_BASE) = (uint8_t) CRC_Data; 来写DR寄存器。正如先前所述,行CRC-> DR被定义为易失性uint32_t,将访问整个寄存器。如果ST更明确地说访问DR寄存器而不仅仅是说支持8位类型,这将非常有帮助。

0

根据我的经验,你不能将STM32 CRC单元输出的CRC32代码与在线CRC32计算器进行比较。

请在此链接中找到我为STM32编写的CRC32计算器。

这个函数已经在我的项目中使用,并被证明是正确的。


0

作为参考,使用低级API获取兼容的crc32:

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;
}

从示例这里进行了微调

LL_CRC_ResetCRCCalculationUnit()重新初始化crc单元,以便重复调用此函数返回预期值,否则初始值将是先前crc的结果,而不是0xFFFFFFFF

你可能不需要将多项式设置为默认值的行,因为它们应该已经从重置中初始化为这些值,但我保留了它们以备后用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接