2013-10-13 75 views
8

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

搜索整个网络,我发现了两个功能,似乎正确计算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 

而且

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

的问题是,我应该得到两个十六进制字节这个函数返回一个整数值的CRC编号。例如,对于“01”(1字节),当我得到“21695”时,我应该得到一个“7E80”,并且我无法对此进行某种类型的转换。

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

我很高兴的任何帮助,

Momergil。

注意:我使用Qt,所以如果能找到一个解决方案实现QByteArray或另一个Qt友好代码,我会很高兴。无论哪种方式,不使用Qt,C或C++的解决方案是无用的:P

+0

格式化,您打印它*十进制*而不是*十六进制*。尝试'std :: cout << std :: hex << value <<'\ n';'。尽管十进制值“21695”与十六进制“0x7e80”不一样。 –

+0

问题与** [this one]类似(http://stackoverflow.com/questions/19358246/crc-ccitt-to-crc16-modbus-implementation/19382244#19382244)**,但用于不同的编程语言。然而,如果你想计算CRC而不使用表格,那么你仍然可能会感兴趣。 – avra

回答

3

根据MODBUS over serial line specification and implementation guide V1.02,CRC发送小端(低字节在前)。

不过,我不知道你是怎么想出CRC的任何十六进制字节的。 MODBUS RTU是一种二进制协议,CRC以两个字节发送,而不是四个十六进制数字!

使用您提供的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; 
} 
+0

感谢您的快速回复。那么,不要忘记在CRC16函数中data.constData()之前需要一个(BYTE *),你是否肯定了这个函数的作用? :P我试图从SimplyModbus.ca的Excel表格中测试它,并且我无法使CRC值匹配(注意:选择产生的QByteArray并在qdebug()中显示为toHex())。我很高兴如果你尝试通过youserlf :) – Momergil

+0

@Momergil:CRC必须在整个框架计算。我编辑了代码来解决这个问题。这表明您正在尝试使用您在工业自动化设置中不了解的代码。我希望没有人会被杀死。 –

+0

哈哈,不用担心:)我只是想知道:在Modbus协议中,需要安装包:salve地址(ok),命令/函数(ok),然后是寄存器编号,然后是数据 - 而您的函数只有数据。在这种情况下,我应该如何将它插入你的函数中?只添加<< quint8(注册)?注意:我无法验证CRC是否正确完成。可以肯定的是,您是否检查过您的CRC计算是否正在使用只需Modbus的CRC计算器? xD(希望你没有因为我而生气)^^) – Momergil

6
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样的小白自己,butttt-

我使用U子提供的代码和测试它自己,并为u说,没有工作权,但后来我意识到这是路过十六进制字符,所以我只是改变了字符,至少为我检查。

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

+0

@Roney Thansk Roney的答案,尽管我会最终使用库巴的版本,因为我已经验证它=] – Momergil

+1

@Momergil:这是亚当的答案。 Roney只是稍微编辑它。 – lpapp

+0

@Momergil:Yeap。学分Adam Lam。 –

2

我尝试使用你在这里发布的代码的第一个例子(使用表的一个),我发现,使用索引时出现错误。为了使代码正确运行,您必须访问受其大小限制的区域中的表格。

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

因此,下面列出了为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 
+0

你说*返回的号码已经交换了Lo和Hi字节*。你确定要提交的代码?我认为这个代码返回正常的CRC字,用户必须在连接到消息帧之前交换字节,如下所示:'wCRCWord =(wCRCWord << 8)| (wCRCWord >> 8)'。例如,考虑一个十六进制的简单消息:'11 03 00 6B 00 03'。这个代码为所考虑的帧计算的CRC值是“87 76”,完整的消息是“11 03 00 6B 00 03 76 87”(例如:http://www.simplymodbus.ca/ASCII.htm ) –

0

这是我的两美分。 第一件事情,你不能这么

  1. 追加两个值返回两个值的函数的阵列(记得去掉常量)
  2. 返回包含这两个值的结构。
  3. Proccess返回值如下

    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 
    

试试这个,让我知道。