C++ 计算 Modbus RTU CRC 16

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19347685/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 22:44:45  来源:igfitidea点击:

Calculating Modbus RTU CRC 16

c++qthexmodbuscrc16

提问by Momergil

I'm implementing a software where I read and write data in Modbus RTU protocolo via serial. For that, I need to calculate the two CRC byte at the end of the string of bytes, but I'm being incapable of doing this.

我正在实现一个软件,在该软件中我通过串行读取和写入 Modbus RTU 协议中的数据。为此,我需要计算字节串末尾的两个 CRC 字节,但我无法这样做。

Searching throughout the web, I found two functions that seems to calculate the CRC correctly:

在整个网络上搜索,我发现了两个似乎可以正确计算 CRC 的函数:

WORD CRC16 (const BYTE *nData, WORD wLength)
{
    static const WORD wCRCTable[] = {
       0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
       0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
       0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
       0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
       0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
       0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
       0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
       0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
       0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
       0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
       0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
       0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
       0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
       0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
       0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
       0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
       0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
       0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
       0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
       0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
       0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
       0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
       0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
       0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
       0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
       0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
       0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
       0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
       0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
       0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
       0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
       0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };

    BYTE nTemp;
    WORD wCRCWord = 0xFFFF;

    while (wLength--)
    {
        nTemp = *nData++ ^ wCRCWord;
        wCRCWord >>= 8;
        wCRCWord  ^= wCRCTable[nTemp];
    }
    return wCRCWord;
} // End: CRC16

And

uint CRC16_2(QByteArray buf, int len)
{
  uint crc = 0xFFFF;

  for (int pos = 0; pos < len; pos++)
  {
    crc ^= (uint)buf[pos];          // XOR byte into least sig. byte of crc

    for (int i = 8; i != 0; i--) {    // Loop over each bit
      if ((crc & 0x0001) != 0) {      // If the LSB is set
        crc >>= 1;                    // Shift right and XOR 0xA001
        crc ^= 0xA001;
      }
      else                            // Else LSB is not set
        crc >>= 1;                    // Just shift right
    }
  }
  // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
  return crc;
}

The problem is that I'm supposed to get two hex bytes as CRC numbers while this functions returns a integer value. For example, for "01" (1 byte), I was supposed to get a "7E80" while I get "21695", and I'm being unable to do some sort of conversion from this to that hex data.

问题是我应该得到两个十六进制字节作为 CRC 数字,而这个函数返回一个整数值。例如,对于“01”(1 个字节),我应该得到“7E80”而我得到“21695”,但我无法从这个到那个十六进制数据进行某种转换。

My question, therefore, is: how do I go from the integer result to the double hex result needed? I tried a couple of options, with no success.

因此,我的问题是:如何从整数结果到所需的双十六进制结果?我尝试了几个选项,但没有成功。

Note: I'm using Qt, so if one could find a solution implementing QByteArray or another Qt friendly code, I'll be glad. Either way a solution not using Qt, C or C++ is useless :P

注意:我正在使用 Qt,所以如果有人能找到实现 QByteArray 或其他 Qt 友好代码的解决方案,我会很高兴。无论哪种方式,不使用 Qt、C 或 C++ 的解决方案都是无用的:P

采纳答案by Reinstate Monica

According to MODBUS over serial line specification and implementation guide V1.02, the CRC is sent little-endian (low byte first).

根据MODBUS over serial line规范和实现指南V1.02,CRC是little-endian(低字节在前)发送的。

I have no idea, though, how you came up with needing any hexadecimal bytes for the CRC. MODBUS RTU is a binary protocol, and the CRC is sent as two bytes, not as four hexadecimal digits!

但是,我不知道您是如何想出需要任何十六进制字节来进行 CRC 的。MODBUS RTU 是一个二进制协议,CRC 是作为两个字节发送的,而不是四个十六进制数字!

Here's how you'd do it, using the CRC16 function you provided.

以下是使用您提供的 CRC16 函数执行此操作的方法。

QByteArray makeRTUFrame(int slave, int function, const QByteArray & data) {
    Q_ASSERT(data.size() <= 252);
    QByteArray frame;
    QDataStream ds(&frame, QIODevice::WriteOnly);
    ds.setByteOrder(QDataStream::LittleEndian);
    ds << quint8(slave) << quint8(function);
    ds.writeRawData(data.constData(), data.size());
    int const crc = CRC16((BYTE*)frame.constData(), frame.size());
    ds << quint16(crc);
    return frame;
}

回答by Adam Lam

unsigned int CRC16_2(unsigned char *buf, int len)
{  
  unsigned int crc = 0xFFFF;
  for (int pos = 0; pos < len; pos++)
  {
  crc ^= (unsigned int)buf[pos];    // XOR byte into least sig. byte of crc

  for (int i = 8; i != 0; i--) {    // Loop over each bit
    if ((crc & 0x0001) != 0) {      // If the LSB is set
      crc >>= 1;                    // Shift right and XOR 0xA001
      crc ^= 0xA001;
    }
    else                            // Else LSB is not set
      crc >>= 1;                    // Just shift right
    }
  }

  return crc;
}

im kind of a noob myself, butttt-

我自己就是个菜鸟,butttt-

i used the code u provided and tested it myself, and as u said it didnt work right, but then i realized it was passing hex chars, so i just changed uint to char and it checks out for me at least.

我使用了你提供的代码并自己测试了它,正如你所说它没有正常工作,但后来我意识到它正在传递十六进制字符,所以我只是将 uint 更改为 char 并且它至少检查了我。

i even calculated a sample by hand to double check.

我什至手工计算了一个样本来仔细检查。

回答by Michal Fiala

I tried using the first example of code you posted in here (the one using table) and I found out, that there is a mistake in using index. To make the code running correctly, you have to access to the table in the area limited by its size.

我尝试使用您在此处发布的第一个代码示例(使用 table 的代码示例),但我发现使用 index.js 时出错。为了使代码正确运行,您必须访问受其大小限制的区域内的表。

wCRCWord  ^= wCRCTable[(nTemp & 0xFF)];

So the whole code, that returns correct value of CRC16 for MODBUS is listed below. The number returned has already swaped Lo and Hi byte.

因此,下面列出了为 MODBUS 返回正确的 CRC16 值的整个代码。返回的数字已经交换了 Lo 和 Hi 字节。

WORD CRC16 (const BYTE *nData, WORD wLength)
{
    static const WORD wCRCTable[] = {
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 };

    BYTE nTemp;
    WORD wCRCWord = 0xFFFF;

    while (wLength--)
    {
        nTemp = *nData++ ^ wCRCWord;
        wCRCWord >>= 8;
        wCRCWord  ^= wCRCTable[(nTemp & 0xFF)];
    }
    return wCRCWord;
} // End: CRC16

回答by Devidas

Here are my two cents. First thing you cant return two values to a function so

这是我的两分钱。首先你不能将两个值返回给一个函数,所以

  1. Append two values to the array(remember to remove const)
  2. Return a struct containing these two values.
  3. Proccess return value as follow

    WORD n = CRC16(nData,wLength);
    WORD x = (0xFF00&n)>>8,y=0x00FF&n;
    printf("0x%04x\n", n); // to check original value
    printf("0x%02x\t0x%02x\n",x,y); // to check separated values
    
  1. 将两个值附加到数组中(记得删除 const)
  2. 返回一个包含这两个值的结构。
  3. 处理返回值如下

    WORD n = CRC16(nData,wLength);
    WORD x = (0xFF00&n)>>8,y=0x00FF&n;
    printf("0x%04x\n", n); // to check original value
    printf("0x%02x\t0x%02x\n",x,y); // to check separated values
    

Try this and let me know.

试试这个,让我知道。