三. VB实现Modbus RTU的几个特述问题 VB提供了标准的通讯控件 MSCOMM,因此,编制串行通讯程序比较简单。有关该控件的应用许多文章都有介绍,这里不再赘述。 由于Modbus RTU模式涉及到一些低层的二进制操作,而VB是一种面向对象的高级语言,因此在实现Modbus RTU时,会碰到一些特殊的问题。 1. 传送二进制字节问题 缺省情况下,VB的MSComm控件的Inputmode属性为0,代表Text。这时,计算机将把接收到的字节视为ASCII字符。而Modbus RTU下,从机返回的将是二进制数,这样,计算机处理数据是就会出错。因此,必须将MSComm控件的该属性改为1,VB将以二进制字节的方式处理收到的数据,就可获得正确的结果。 2.CRC-16的计算 CRC(循环冗余码)是一种漏检率极低的校验方法,它通过由报文数据组成的多项式对特定的生成多项式进行二进制除法,发送方将余数作为校验码发送,接收方以接收到的数据组成多项式对同一生成多项式作除法计算,然后视余数是否为零来决定传输是否出错。如传输过程中未发生错误,则余数应该为零。CRC-16是指生成多项式为:X16+X12+X5+1.的CRC校验方法。 CRC-16能检查出所有的1位,2位出错;所有字节数为奇数的报文的出错;所有字节数少于16位的报文的出错;对17位以上报文的正确检错概率为99.997以上。正因如此,CRC-16是通讯中最常用的检错方法之一。 Modbus协议给出了CRC-16的计算方法,以伪代码形式表示如下: Initialize the CRC (16-bit register) to H’FFFF. Enter the first to the last byte of the message : CRC XOR <byte> —> CRC Enter 8 times Move the CRC one bit to the right If the output bit = 1, enter CRC XOR H’A001—> CRC End enter End enter 首先将校验结果(CRC)初始化为十六进制数“FFFF”。然后,对报文的每个字节做如下运算:首先与当前的CRC作异或运算,然后再将运算结果做8次右移,如果移出的一位为1,则将当前的CRC和十六进制数”A001”作异或运算,结果代回CRC。直至8次移位运算全部完成。对报文中的每个字节进行上述计算,最后的结果就是需要求取的CRC项。 以下是一个用VB编的用于进行CRC-16运算的函数:
Public Function crc(data() As Byte, stanum As Integer, num As Integer) As Long
crc = &HFFFF& For i = stanum To num crc = crc Xor data(i) For j = 1 To 8 lsp = crc And &H1 '取将要移出的一位。 crc = crc \ 2 crc = crc And &H7FFF If lsp = 1 Then crc = crc Xor &HA001& '同样因为HA001的首位为1 End If Next j Next i
End Function VB并没有提供二进制移位算法。由于右移相当于除以2,因此,上例中用“\2”代替右移运算。还有一点值得注意:当使用十六进制立即数,而其最高位为“1”(如上例中的H’FFFF,H’A001等)时,需在数据后加上”&”,以将此立即数声明为BYTE类型,否则VB自动认为这些数据为整型数,而把最高位为”1”的那些数看作负数来进行“\2”运算,将得不到正确的结果。 3.负数处理 Modbus协议规定,负数以补码表示。在有的变频器中(如施耐德公司的PDL变频器),以负数表示反向值,如+50表示正向速度为50个单位,则-50就表示反方向50单位大小的速度。VB的MSComm控件无法自动分辨负数,必须由用户自己编程解决。由于补码为原码求反后加1,同时,有的数据占用两个以上的字节,因此编程时要将接收到的字节先求反,再组合成一个数,然后再加1,即可求得负数的绝对值,然后再按需要对此值进行处理。下面的程序完成接收一个两字节负数,再进行显示的功能。 Private Sub cmdRec_Click() Dim buf$ Dim data As Long Dim FlagOfNeg As Boolean Dim neg1 As Integer Dim neg2 As Integer Dim spset As Single
InByte = MSComm.Input If InByte(3) >= 128 Then FlagOfNeg = True Else FlagOfNeg = False End If
If FlagOfNeg Then
neg1 = (Not InByte(3)) neg2 = (Not InByte(4)) data = (neg1 And &HFF&) * 256 + (neg2 And &HFF&) + 1 spset = data / 2048 * 250 txtSet.Text = "-" + CStr(spset \ 10) + "." + CStr(spset Mod 10) + "%" Else
data = (InByte(3) And &HFF&) * 256 + (InByte(4) And &HFF&) spset = data / 2048 * 250 txtSet.Text = CStr(spset \ 10) + "." + CStr(spset Mod 10) + "%" End If