关于 crc校验 REFIN REFOUT 之类的基础知识就不讲解了,网上有很多。我就直接贴代码了。
下面是满足 CRC 生成多项式为 CRC-16:‘x16+x15+x2+1’ 的 CRC-16-MODBUS 算法代码。

python 代码如下:

# CRC-16-MODBUS
def calculate_crc16(data: bytes) -> int:
    # 初始化crc为0xFFFF
    crc = 0xFFFF

    # 循环处理每个数据字节
    for byte in data:
        # 将每个数据字节与crc进行异或操作
        crc ^= byte

        # 对crc的每一位进行处理
        for _ in range(8):
            # 如果最低位为1,则右移一位并执行异或0xA001操作(即0x8005按位颠倒后的结果)
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            # 如果最低位为0,则仅将crc右移一位
            else:
                crc = crc >> 1

    # 返回最终的crc值
    return crc


if __name__ == '__main__':

    # 测试数据
    test_data = bytes.fromhex("31 32 33")
    # test_data = bytes("Hello, world!", encoding='utf-8')

    # 计算CRC-16校验码
    crc16 = calculate_crc16(test_data)

    # 输出校验码值
    print(f'CRC-16校验码值为: 0x{crc16:04X}')

c 代码如下:

// CRC-16-MODBUS
#include <stdio.h>
#include <stdint.h>

uint16_t calculate_crc16(const uint8_t *data, size_t len) {
    // printf("%d\n",len);
    
    // 初始化crc为0xFFFF
    uint16_t crc = 0xFFFF;

    // 循环处理每个数据字节
    for (size_t i = 0; i < len; i++) {
        // 将每个数据字节与crc进行异或操作
        crc ^= data[i];

        // 对crc的每一位进行处理:如果最低位为1,则右移一位并执行异或0xA001操作(即0x8005按位颠倒后的结果)
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } 
            // 如果最低位为0,则仅将crc右移一位
            else {
                crc = crc >> 1;
            }
        }
    }
    return crc;
}

int main() {
    // 测试数据
    unsigned char data[] = {0x31, 0x32, 0x33};
    size_t len = sizeof(data);

    // 计算CRC-16校验码
    uint16_t crc16 = calculate_crc16(data, len);

    // 输出结果
    // printf("Data: %s\n", data);
    printf("CRC-16: 0x%04X\n", crc16);
    return 0;
}

比较疑惑的点是 CRC-16-MODBUS 算法是需要在计算前将原始数据 data 和运算完成后得到的 CRC 进行反转的,上述代码未能体现这些流程,而是在运算时使用反转后的 crc 值及对 crc 的低位操作,maybe 有异曲同工之妙,学艺不精没有搞懂。下面给出实实在在的按流程走的代码,得到的结果是一样的。

c 代码如下:

// CRC-16-MODBUS
#include <stdio.h>
#include <stdint.h>

// 反转一个8位无符号整数的字节序,即高低字节互换
void InvertUint8(unsigned char *invertBuf)
{
    // 将8位无符号整数指针强制转换为16位无符号整数指针
    unsigned short *srcBuf = (unsigned short *)invertBuf;
    int i;
    unsigned char tmp[4]={0};

    for(i=0;i< 8;i++)
    {
        if(srcBuf[0]& (1 << i))
            tmp[0]|=1<<(7-i);
    }
    invertBuf[0] = tmp[0];
}

// 反转一个16位无符号整数的字节序,即高低字节互换
void InvertUint16(unsigned short *invertBuf)
{
    // 将16位无符号整数指针强制转换为16位无符号整数指针
    unsigned short *srcBuf = (unsigned short *)invertBuf;
    int i;
    unsigned short tmp[4]={0};

    for(i=0;i< 16;i++)
    {
        if(srcBuf[0]& (1 << i))
            tmp[0]|=1<<(15 - i);
    }
    invertBuf[0] = tmp[0];
}

// 计算给定数据的CRC-16校验码
uint16_t calculate_crc16(const uint8_t *data, size_t len)
{
    uint16_t crc = 0xFFFF;
    unsigned char invertData = 0;

    // 循环处理每个字节
    while (len--)    
    {
        invertData = *(data++);
        InvertUint8(&invertData);  // 反转字节序
        crc ^= (invertData << 8);  // 异或运算
        for(int i = 0;i < 8;i++)
        {
            if(crc & 0x8000)  // 判断高位是否为1
                crc = (crc << 1) ^ 0x8005;  // 左移1位并与多项式0x8005进行异或运算
            else
                crc = crc << 1;  // 左移1位
        }
    }
    InvertUint16(&crc);  // 反转字节序
    return (crc);
}

int main()
{
    unsigned char data[] = {0x31, 0x32, 0x33};  // 待计算CRC-16的数据
    size_t len = sizeof(data);  // 数据长度

    uint16_t crc16 = calculate_crc16(data, len);  // 计算CRC-16校验码

    // 输出CRC-16校验码
    printf("CRC-16: 0x%04X\n", crc16);

    return 0;
}

参考文章:

常用CRC校验计算代码示例_柒壹漆的博客-CSDN博客

CRC校验详解(附代码示例)_crc校验代码_fengwang0301的博客-CSDN博客

CRC校验原理及实现 - 知乎 (zhihu.com)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐