【问题标题】:Checksum for binary PLC communication二进制 PLC 通信的校验和
【发布时间】:2013-04-01 14:42:13
【问题描述】:

我一直在摸索计算校验和以使用二进制命令与 Unitronics PLC 通信。他们提供了源代码,但它是一个仅限 Windows 的 C# 实现,除了基本语法之外,这对我没什么帮助。

Specification PDF(校验和计算接近尾声)

C# driver source(Utils.cs 中的校验和计算)

预期结果

下面是字节索引、消息描述和有效的示例。

#  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 24 25 26 27 28 29 | 30 31 32
# sx--------------- id FE 01 00 00 00 cn 00 specific--------- lengt CHKSM | numbr ot FF addr- | CHKSM ex
# 2F 5F 4F 50 4C 43 00 FE 01 00 00 00 4D 00 00 00 00 00 01 00 06 00 F1 FC | 01 00 01 FF 01 00 | FE FE 5C

规范要求计算 22 字节消息头的累加值,以及单独的 6+ 字节详细信息,得到 sum 模 65536 的值,然后返回该值的二进制补码。

尝试 #1

我的理解是Python中的波浪号(~)操作符是直接从C/C++派生而来的。在编写了创建消息的 Python 一天后,我想出了这个(精简版):

#!/usr/bin/env python

def Checksum( s ):
    x = ( int( s, 16 ) ) % 0x10000
    x = ( ~x ) + 1 
    return hex( x ).split( 'x' )[1].zfill( 4 )

Details = ''
Footer  = ''
Header  = ''
Message = ''

Details += '0x010001FF0100'

Header += '0x2F5F4F504C4300FE010000004D000000000001000600'
Header += Checksum( Header )

Footer += Checksum( Details )
Footer += '5C'

Message +=  Header.split( 'x' )[1].zfill( 4 )
Message += Details.split( 'x' )[1].zfill( 4 )
Message +=  Footer

print Message

留言:2F5F4F504C4300FE010000004D000000000001000600600L010001FF010001005C

我在其中看到了一个 L,这与昨天的结果不同,而且没有更接近。如果您想要基于消息的其余部分的快速公式结果:Checksum(Header) 应该返回 F1FC 并且 Checksum(Details) 应该返回 FEFE

它返回的值与规范中的示例相差甚远。我认为问题可能是一两件事:Checksum 方法没有正确计算十六进制字符串的总和,或者 Python ~ 运算符不等同于 C++ ~ 运算符。

尝试 #2

一个朋友给了我他对计算应该是什么的 C++ 解释,我只是无法理解这段代码,我的 C++ 知识很少。

short PlcBinarySpec::CalcHeaderChecksum( std::vector<byte>  _header ) {
    short bytesum = 0;
    for ( std::vector<byte>::iterator it = _header.begin(); it != _header.end(); ++it ) {
        bytesum = bytesum + ( *it );
    }
    return ( ~( bytesum % 0x10000 ) ) + 1;
}

【问题讨论】:

  • ~ 在 C 语言中的 Python a 中并不意味着相同的东西,因为 Python 的 int 类型与 C 的 short 非常不同。 A C short 是一个 16 位数字,(在您的平台上)如果溢出则回绕; Python int 是一个无限位数,如果溢出就会变大。大多数使用 ~ 的 C 代码都明确依赖于该环绕。
  • 同时,请告诉我们一些输入的预期输出是什么;如果我们可以将错误答案与正确答案进行比较,那么调试代码要比必须猜测答案的错误之处要容易得多。
  • 特别是:代码中的Checksum(Header) 是否应该返回 08fb、f705、ffa9 或其他内容?
  • 简答:Checksum(Header) 应该返回 F1 FC 并且 Checksum(Details) 应该返回 FE FE。

标签: c++ python hex checksum plc


【解决方案1】:

我不完全确定正确的代码应该是什么……但如果意图是让Checksum(Header) 返回 f705,而它返回的是 08fb,那么问题来了:

x = ( ~( x % 0x10000 ) ) + 1

简短的版本是你想要这个:

x = (( ~( x % 0x10000 ) ) + 1) % 0x10000

这里的问题不是~ 意味着不同的东西。正如the documentation 所说,~x 返回“x 反转的位”,这实际上与它在 C 中的含义相同(至少在 2s 补码平台上,包括所有 Windows 平台)。

您可能会在此处遇到 C 和 Python 类型之间差异的问题(C 整数类型是固定大小的,并且会溢出;Python 整数类型实际上是无限大小的,并且可以根据需要增长)。但我认为这不是你的问题。

问题只是如何将结果转换为字符串。

调用Checksum(Header) 的结果,直到格式化,在两个版本中都是-2299 或-0x08fb。

在 C 语言中,您几乎可以将有符号整数视为相同大小的无符号整数(尽管在某些情况下您可能必须忽略警告才能这样做)。确切的作用取决于您的平台,但在 2s 补码平台上,有符号短 -0x08fb 是无符号 0xf705 的逐位等效项。因此,例如,如果您使用 sprintf(buf, "%04hx", -0x08fb),它就可以正常工作——并且它为您(在大多数平台上,包括所有 Windows 上)提供了未签名的等价物 f705

但在 Python 中,没有无符号整数。 int -0x08fb 与 0xf705 无关。如果你做"%04hx" % -0x08fb,你会得到-8fb,并且没有办法强制“将其转换为未签名”或类似的东西。

你的代码实际上是hex(-0x08fb),它给你-0x8fb,然后你在x上给你split,给你8fb,你zfill08fb,这就造成了问题有点难以注意到(因为这看起来像是一对完全有效的十六进制字节,而不是一个减号和三个十六进制数字),但这是同一个问题。

无论如何,您必须明确确定“无符号等价物”的含义,并编写代码来执行此操作。由于您试图匹配 C 在 2s 补码平台上所做的事情,因此您可以将该显式转换写为 % 0x10000。如果您执行"%04hx" % (-0x08fb % 0x10000),您将获得f705,就像您在C 中所做的那样。对于您现有的代码也是如此。

【讨论】:

  • 虽然这肯定不是复制粘贴工作,但这个答案帮助我找出了哪里出错了。一旦我修改了校验和以正确计算十六进制字符串,第二个模数就会返回正确的值。谢谢!
【解决方案2】:

这很简单。我通过在计算器上手动添加所有标题字节来检查您朋友的算法,它产生了正确的结果 (0xfcf1)。

现在,我实际上并不了解 python,但在我看来,您正在将半字节值相加。你的标题字符串是这样的:

Header = '2F5F4F504C4300FE010000004D000000000001000600'

然后您将该字符串中的每个字节从十六进制转换并添加。这意味着您正在处理从 0 到 15 的值。您需要将每两个字节视为一对并将其转换(从 0 到 255 的值)。或者您需要使用实际的二进制数据而不是二进制数据的文本表示。

在算法结束时,如果您不信任它,您实际上不需要执行 ~ 运算符。相反,您可以使用(0xffff - (x % 0x10000)) + 1。请记住,在加 1 之前,该值实际上可能是 0xffff,因此您需要在之后将整个结果与 0x10000 取模。你朋友的 C++ 版本使用 short 数据类型,所以根本不需要取模,因为 short 会自然溢出

【讨论】:

  • 感谢替代公式,我已在两个十六进制字符串前面加上 0x 以解决字节值问题。
猜你喜欢
  • 2020-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 2015-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多