|
发表于 2010-5-8 18:24:00
|
显示全部楼层
三菱FX系列PLC编程口通信协议
<P align=left>该协议实际上适用于PLC编程端口以及 FX-232AW 模块的通信。<BR>
<P align=left><B>通讯格式:</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD width="38%">
<P align=center>命令<BR></P></TD>
<TD width="13%">
<P align=center>命令码<BR></P></TD>
<TD width="49%">
<P align=center>目标设备<BR></P></TD></TR>
<TR>
<TD width="38%">
<P align=center>DEVICE READ CMD<BR></P></TD>
<TD width="13%">
<P align=center>"0"<BR></P></TD>
<TD width="49%">
<P align=center>X,Y,M,S,T,C,D<BR></P></TD></TR>
<TR>
<TD width="38%">
<P align=center>DEVICE WRITE CMD<BR></P></TD>
<TD width="13%">
<P align=center>"1" <BR></P></TD>
<TD width="49%">
<P align=center>X,Y,M,S,T,C,D<BR></P></TD></TR>
<TR>
<TD width="38%">
<P align=center>FORCE ON CMD<BR></P></TD>
<TD width="13%">
<P align=center>" 7"<BR></P></TD>
<TD width="49%">
<P align=center>X,Y,M,S,T,C<BR></P></TD></TR>
<TR>
<TD width="38%">
<P align=center>FORCE OFF CMD <BR></P></TD>
<TD width="13%">
<P align=center>"8" <BR></P></TD>
<TD width="49%">
<P align=center>X,Y,M,S,T,C<BR></P></TD></TR></TBODY></TABLE>
<P align=left><B>传输格式:</B> RS232C<B>波特率:</B> 9600bps<B>奇偶:</B> even<B>校验:</B> 累加方式(和校验)<B>字符: </B>ASCII <BR>
<P align=left><B>16</B><B>进制代码:</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>ENQ<BR></P></TD>
<TD>
<P align=center>05H<BR></P></TD>
<TD>
<P align=center>请求<BR></P></TD></TR>
<TR>
<TD>
<P align=center>ACK<BR></P></TD>
<TD>
<P align=center>06H<BR></P></TD>
<TD>
<P align=center>PLC正确响应<BR></P></TD></TR>
<TR>
<TD>
<P align=center>NAK<BR></P></TD>
<TD>
<P align=center>15H<BR></P></TD>
<TD>
<P align=center>PLC错误响应<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>02H<BR></P></TD>
<TD>
<P align=center>报文开始<BR></P></TD></TR>
<TR>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>03H<BR></P></TD>
<TD>
<P align=center>报文结束<BR></P></TD></TR></TBODY></TABLE>
<P align=left><BR>
<P align=center><B>三菱</B><B>FX</B><B>系列</B><B>PLC</B><B>编程口通信协议举例</B><BR>
<P align=left><B>1</B><B>、DEVICE READ(读出软设备状态值)</B><BR>
<P align=left>计算机向PLC发送: <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>首地址<BR></P></TD>
<TD>
<P align=center>位数<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>GROUP ADDRESS<BR></P></TD>
<TD>
<P align=center>BYTES<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR></TBODY></TABLE>
<P align=left><B>2</B><B>、DEVICE WRITE(向PLC软设备写入值)</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=left>始<BR></P></TD>
<TD>
<P align=left>命令<BR></P></TD>
<TD>
<P align=left>首地址<BR></P></TD>
<TD>
<P align=left>位数<BR></P></TD>
<TD colSpan=4>
<P align=center>数据<BR></P></TD>
<TD>
<P align=left>终<BR></P></TD>
<TD>
<P align=left>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=left>STX<BR></P></TD>
<TD>
<P align=left>CMD<BR></P></TD>
<TD>
<P align=left>GROUP ADDRESS<BR></P></TD>
<TD>
<P align=left>BYTES<BR></P></TD>
<TD>
<P align=left>1ST DATA<BR></P></TD>
<TD>
<P align=left>2ND DATA<BR></P></TD>
<TD>
<P align=left>......<BR></P></TD>
<TD>
<P align=left>LAST DATA<BR></P></TD>
<TD>
<P align=left>ETX<BR></P></TD>
<TD>
<P align=left>SUM<BR></P></TD></TR>
<TR>
<TD width=36><BR> <BR></TD>
<TD width=42><BR> <BR></TD>
<TD width=135><BR> <BR></TD>
<TD width=56><BR> <BR></TD>
<TD width=78><BR> <BR></TD>
<TD width=83><BR> <BR></TD>
<TD width=32><BR> <BR></TD>
<TD width=92><BR> <BR></TD>
<TD width=36><BR> <BR></TD>
<TD width=55><BR> <BR></TD></TR></TBODY></TABLE>
<P align=left><B>3</B><B>、位设备强制置位/复位</B><BR>
<P align=left><B>FORCE ON</B><B>置位</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>地址<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>ADDRESS<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>37h<BR></P></TD>
<TD>
<P align=center>address<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>sum<BR></P></TD></TR></TBODY></TABLE>
<P align=left><B>FORCE OFF</B><B>复位</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>地址<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>ADDRESS<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>38h<BR></P></TD>
<TD>
<P align=center>address<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>sum<BR></P></TD></TR></TBODY></TABLE>
<P align=left> <B>说明:</B><BR>
<P align=left>1.帧中的BYTES表示需要读取或者写入的字节数。<BR>
<P align=left>2.地址算法上有说明。<BR>
<P align=left>3.累加和是从STX后面一个字节开始累加到ETX的和。<BR>
<P align=left> 这个就是网上流传的的比较完整的通讯协议说明了,可能对刚刚入门的朋友有点难度。因为光看这些对一个编程的初学者不知道如何下手,那么我将在下面的文章把不同地址的如何读写详细说明一下。<BR>
<P align=left>上次的文章主要讲述了三菱PLC的协议以及一些格式。但是如何来利用这个协议来写自己的程序呢?<BR>我现在就讲述利用VB如何对PLC的地址读写和置位与复位等等。<BR>
<P align=left> 首先,在这里我先讲述一下对bit的地址置位和复位操作。我要对bit位进行置位复位我们就要了结一下bit位的地址概念。<BR>
<P align=left> bit位包括S,X,Y,T,M,C这些内部位,那么这些地址在PLC里面是如何存放的呢?学过计算机的人都应该知道计算机里面只有0和1,把这些0和1按不同的顺序排列就能组成要计算机语言,在计算机里面也有地址,PLC也是一台简单的计算机所以和它的原理是一样的这些bit位都是被有序的存储起来的。通过我的试验发现三菱FX系列PLC地址如下:<BR>
<P align=left> S就是系统位,在三菱FX系列里面开始是S0点被存储在地址0008(H),X0是0408(H),Y0是0508,T0是0708,M0是0808,C0是0D08。这样就对PLC的内部地址有了一定的了解,知道的bit位的地址我们如何来置位复位呢?对地址的通讯协议是这样的:<BR>
<P align=left><B>位设备</B><B>强制置位/复位</B><BR>
<P align=left><B>FORCE ON</B><B>置位</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>地址<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>ADDRESS<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>37h<BR></P></TD>
<TD>
<P align=center>address<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>sum<BR></P></TD></TR></TBODY></TABLE>
<P align=left><B>FORCE OFF</B><B>复位</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>地址<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>ADDRESS<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>38h<BR></P></TD>
<TD>
<P align=center>address<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>sum<BR></P></TD></TR></TBODY></TABLE>
<P align=left> <B>PLC</B><B>返回</B><BR>
<P align=left>ACK(06H) 接受正确NAK(15H) 接受错误<BR>
<P align=left><BR>
<P align=left> 有了格式我们就很清楚的来实现置位复位功能了。那么我们先做一些准备工作。上面的协议可能读者对和效验不太清楚那么我来解释一下,所谓和效验就是为了保证通讯的正确性,下面就是一个例子:<BR>
<P align=left>例子:<BR>
<P align=left>STX ,CMD ,ADDRESS, BYTES, ETX, SUM<BR>
<P align=left>02H, 30H, <B><U>31H,30H,46H,36H</U></B>, 30H,34H, 03H, 37H,34H<BR>
<P align=left>SUM=CMD ...... ETX;<BR>
<P align=left>30h 31h 30h 46h 36h 30h 34h 03h=74h;<BR>
<P align=left>累加和超过两位取低两位<BR>
<P align=left>累加和是从STX后面一个字节开始累加到ETX的和<BR>
<P align=left> 那么我们做一个小程序来实现它<BR>
<P align=left>**************************<BR>* 和校验<BR>*Check_FCS(mStr)<BR>*mStr 需要检查的字符串<BR>**************************<BR>Public Function Check_FCS(mStr As String) As String<BR> Dim K As Integer, mTest As Integer<BR> For K = 1 To Len(mStr)<BR> mTest = mTest Asc(Mid(mStr, K, 1))<BR> Next<BR> Check_FCS = Right(Hex(mTest), 2)<BR>End Function<BR>
<P align=left> 现在可以对bit位进行置位了程序如下:<BR>
<P align=left>****************************<BR>*强置bit位地址<BR>*SetM(address)<BR>****************************<BR>Public Function SetM(address As String) As Boolean<BR> Dim Q As Long, myTest As String, myHex As String, QQ As String, mStr As Integer, Num As Integer<BR> On Error GoTo SetMErr<BR> select Case Left(address, 1)<BR> Case "M"<BR> mStr = 2048<BR> Case "X"<BR> mStr = 1024<BR> Case "Y"<BR> mStr = 1280<BR> Case "S"<BR> mStr = 0<BR> Case "T"<BR> mStr = 1536<BR> Case "C"<BR> mStr = 3584<BR> End Select<BR> If mStr = 1024 Then<BR> Q = OCT_to_DEC(Mid(address, 2)) mStr<BR> ElseIf mStr = 1280 Then<BR> Q = OCT_to_DEC(Mid(address, 2)) mStr<BR> Else<BR> Q = Val(Mid(address, 2)) mStr 这个算法只能写到M1741点 address * 256 8只能写到M255点<BR> End If<BR> QQ = Hex(Q)<BR> If Len(QQ) = 3 Then<BR> QQ = "0" & QQ<BR> ElseIf Len(QQ) = 2 Then<BR> QQ = "00" & QQ<BR> ElseIf Len(QQ) = 1 Then<BR> QQ = "000" & QQ<BR> End If<BR> QQ = Right(QQ, 2) & Left(QQ, 2) 换位操作 地位在前高位在后<BR> myHex = "7" & QQ & Chr(3) 置位操作7是命令置位命令 则8是复位操作<BR> myHex = Chr(2) & myHex & Check_FCS(myHex)<BR> Form1.MSComm.Output = myHex<BR> Call Sleep(Tim)<BR> myTest = Form1.MSComm.Input<BR> If Asc(myTest) = 6 Then SetM = True<BR> Exit Function<BR>SetMErr:<BR> SetM = False<BR>End Function<BR>****************************<BR>*复位bit位地址<BR>*ResetM(address)<BR>****************************<BR>Public Function ResetM(address As String) As Boolean<BR> Dim Q As Long, myTest As String, myHex As String, QQ As String, mStr As Integer<BR> On Error GoTo ResetMErr<BR> <BR> select Case Left(address, 1)<BR> Case "M"<BR> mStr = 2048<BR> Case "X"<BR> mStr = 1024<BR> Case "Y"<BR> mStr = 1280<BR> Case "S"<BR> mStr = 0<BR> Case "T"<BR> mStr = 1536<BR> Case "C"<BR> mStr = 3584<BR> End Select<BR> <BR> If mStr = 1024 Then<BR> Q = OCT_to_DEC(Mid(address, 2)) mStr<BR> ElseIf mStr = 1280 Then<BR> Q = OCT_to_DEC(Mid(address, 2)) mStr<BR> Else<BR> Q = Val(Mid(address, 2)) mStr 这个算法只能写到M1535点 address * 256 8只能写到M255点<BR> End If<BR> QQ = Hex(Q)<BR> If Len(QQ) = 3 Then<BR> QQ = "0" & QQ<BR> ElseIf Len(QQ) = 2 Then<BR> QQ = "00" & QQ<BR> ElseIf Len(QQ) = 1 Then<BR> QQ = "000" & QQ<BR> End If<BR> QQ = Right(QQ, 2) & Left(QQ, 2)<BR> myHex = "8" & QQ & Chr(3)<BR> myHex = Chr(2) & myHex & Check_FCS(myHex)<BR> Form1.MSComm.Output = myHex<BR> Call Sleep(Tim)<BR> myTest = Form1.MSComm.Input<BR> If Asc(myTest) = 6 Then ResetM = True<BR> Exit Function<BR>ResetMErr:<BR> ResetM = False<BR>End Function<BR>
<P align=left> 这里我写成了一个函数形式,方便以后随时调用下篇将继续介绍对D的地址位的读写操作等。<BR>
<P align=left>这次我们来说一下对三菱FX系列PLC的D地址的读写操作,先来看看他的通讯协议如下:<BR>
<P align=left><B>1</B><B>、DEVICE READ(读出软设备状态值)</B><BR>
<P align=left>计算机向PLC发送: <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>首地址<BR></P></TD>
<TD>
<P align=center>位数<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>GROUP ADDRESS<BR></P></TD>
<TD>
<P align=center>BYTES<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR></TBODY></TABLE>
<P align=left>例子:从D123开始读取4个字节数据 <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>30h<BR></P></TD>
<TD>
<P align=center><B><U>31h,30h,46h,36h</U></B><BR></P></TD>
<TD>
<P align=center>30h,34h<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>37h,34h<BR></P></TD></TR></TBODY></TABLE>
<P align=left>地址算法:address=address*2 1000h<BR>
<P align=left>再转换成ASCII<BR>
<P align=left>31h,30h,46h,36h<BR>
<P align=left><B>PLC</B><B>返回</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>1ST DATA<BR></P></TD>
<TD>
<P align=center>2ND DATA<BR></P></TD>
<TD>
<P align=center>.....<BR></P></TD>
<TD>
<P align=center>LAST DATA<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR></TBODY></TABLE>
<P align=left>注:最多可以读取64个字节的数据<BR>
<P align=left>例子:从指定的存储器单元读到3584这个数据 <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center><B><I>33h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>35h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>38h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>34h</I></B><BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>44h,36h<BR></P></TD></TR></TBODY></TABLE>
<P align=left><BR>
<P align=left><B>2</B><B>、DEVICE WRITE(向PLC软设备写入值)</B> <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=left>始<BR></P></TD>
<TD>
<P align=left>命令<BR></P></TD>
<TD>
<P align=left>首地址<BR></P></TD>
<TD>
<P align=left>位数<BR></P></TD>
<TD colSpan=4>
<P align=center>数据<BR></P></TD>
<TD>
<P align=left>终<BR></P></TD>
<TD>
<P align=left>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=left>STX<BR></P></TD>
<TD>
<P align=left>CMD<BR></P></TD>
<TD>
<P align=left>GROUP ADDRESS<BR></P></TD>
<TD>
<P align=left>BYTES<BR></P></TD>
<TD>
<P align=left>1ST DATA<BR></P></TD>
<TD>
<P align=left>2ND DATA<BR></P></TD>
<TD>
<P align=left>......<BR></P></TD>
<TD>
<P align=left>LAST DATA<BR></P></TD>
<TD>
<P align=left>ETX<BR></P></TD>
<TD>
<P align=left>SUM<BR></P></TD></TR>
<TR>
<TD width=37><BR> <BR></TD>
<TD width=44><BR> <BR></TD>
<TD width=141><BR> <BR></TD>
<TD width=59><BR> <BR></TD>
<TD width=81><BR> <BR></TD>
<TD width=86><BR> <BR></TD>
<TD width=33><BR> <BR></TD>
<TD width=95><BR> <BR></TD>
<TD width=38><BR> <BR></TD>
<TD width=57><BR> <BR></TD></TR></TBODY></TABLE>
<P align=left>例子:向D123开始的两个存储器中写入1234,ABCD <BR>
<TABLE border=1 cellSpacing=0 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=left>02h<BR></P></TD>
<TD>
<P align=left>31h<BR></P></TD>
<TD>
<P align=left><B><U>31h,30h,46h,36h</U></B><BR></P></TD>
<TD>
<P align=left><B><I>30h,34h</I></B><BR></P></TD>
<TD>
<P align=left><B><I>33h,34h,31h,32h,43h,44h,41h,42h</I></B><BR></P></TD>
<TD>
<P align=left>03h<BR></P></TD>
<TD>
<P align=left>34h,39h<BR></P></TD></TR></TBODY></TABLE>
<P align=left><B>PLC</B><B>返回</B><BR>
<P align=left>ACK (06H) 接受正确<BR>
<P align=left>NAK (15H) 接受错误<BR>
<P align=left><BR>
<P align=left> 上面有一个例子已经讲述的比较明确了,下面就用vb写一个例子来实现这样的功能。<BR>
<P align=left>首先,做一个补0操作的函数为以后作准备:<BR>
<P align=left>**************************<BR>* 补0操作<BR>*Check_Hex(mStr)<BR>*mStr 可能需要补0的数据<BR>**************************<BR>Private Function Check_Hex(mIndex As Integer) As String<BR> If mIndex < 16 Then Check_Hex = "0" & Hex(mIndex) Else Check_Hex = Hex(mIndex)<BR>End Function<BR>
<P align=left> 好下面就来写一个对D地址的写操作函数<BR>
<P align=left>*******************************<BR>*写入PLC<BR>*Write_PLC(mData(),mStart)<BR>*mData() 写入的数据--以数组形式来表示<BR>*mStart 写入的首地址<BR>*******************************<BR>Public Function Write_PLC(mData() As Integer, mStart As Integer) As Boolean<BR> Dim myHex As String, Q As Integer, myTest As String, mCounts As Integer<BR> On Error GoTo WriteErr<BR> Q = mStart * 2 &H1000 地址转换<BR> mCounts = (UBound(mData) 1) * 2 写几个D的地址<BR> myHex = "1" & Hex(Q) & Check_Hex(mCounts) 定义的一个函数来对写入的值进行补0操作<BR> For Q = 0 To UBound(mData)<BR> myHex = myHex & Check_Hex(mData(Q) Mod 256) & Check_Hex(mData(Q) \ 256)<BR> Next<BR> myHex = myHex & Chr(3)<BR> myHex = Chr(2) & myHex & Check_FCS(myHex)<BR> Form1.MSComm.Output = myHex<BR> Call Sleep(Tim)<BR> myTest = Form1.MSComm.Input<BR> Debug.Print myHex, Asc(myTest)<BR> If Asc(myTest) = 6 Then Write_PLC = True<BR> Exit Function<BR>WriteErr:<BR> Write_PLC = False<BR>End Function<BR>
<P align=left> 再来写一个读D地址的函数<BR>
<P align=left>***************************************<BR>*读取PLC<BR>*Read_PLC(mData(),mStart,mCounts)<BR>*mData() 读出的数据--以数组的形式表示<BR>*mSstar 起始读的地址位<BR>*mCounts 读几位地址数据<BR>***************************************<BR>Public Function Read_PLC(mData() As Integer, mStart As Integer, mCounts As Integer) As Boolean<BR> Dim myHex As String, Q As Integer, myTest As String<BR> On Error GoTo ReadErr<BR> Q = mStart * 2 &H1000 地址转换<BR> mCounts = mCounts * 2<BR> ReDim mData(mCounts / 2 - 1) 定义数组的数据个数<BR> myHex = "0" & Hex(Q) & Check_Hex(mCounts) & Chr(3)<BR> myHex = Chr(2) & myHex & Check_FCS(myHex)<BR> Form1.MSComm.Output = myHex<BR> Call Sleep(Tim)<BR> myTest = Form1.MSComm.Input<BR> Debug.Print myHex, myTest<BR> 检查是否传送有错误<BR> If Len(myTest) < 4 2 * mCounts Then GoTo ReadErr<BR> If Right(myTest, 2) <> Check_FCS(Mid(myTest, 2, 2 * mCounts 1)) Then GoTo ReadErr<BR> Read_PLC = True<BR> For Q = 0 To (mCounts \ 2) - 1<BR> mData(Q) = "&H" & (Mid(myTest, 4 Q * 4, 2) & Mid(myTest, 2 Q * 4, 2)) 把读到的数据写进数组<BR> Next<BR> Exit Function<BR>ReadErr:<BR> Read_PLC = False<BR>End Function<BR>
<P align=left> 这两个函数就可以实现对D地址的读写操作了,当然D的地址分16bit、32bit两种具体编程方法是一样的。<BR>
<P align=left>我们已经介绍了对三菱FX系列PLC的bit位置位和D地址的读写,下面我们来介绍一下对bit位的查询状态操作如何实现。<BR>
<P align=left> 可能有人会说查询bit位状态很简单,就是通过协议的chr(03)读操作来实现。是的没错,但是我有些地方要详细说明一下。首先,bit位的查询和对它的置位、复位地址是不一样的,之前我们介绍了S,X,Y,M,T,C的地址说明,但是那些地址只适用在置位、复位功能上。查询的地址是如下:<BR>
<P align=left> S0~S7=0000 X0~X7=0080 Y0~Y7=00A0 T0~T7=00C0 M0~M7=0100 C0~C8=01C0<BR>
<P align=left>其他循序排列,可能有人会问为什么是0~7,读操作是一个字来读取的也就是说一次就要读8位,如果要判断某一位的状态时,程序就要做一个简单的判断。<BR>
<P align=left> 我们了解了PLC内部的地址问题就好来编程了,具体代码如下:<BR>
<P align=left>***************************************<BR>*读取PLC的状态(X,Y,M,S,T,C)<BR>*ReadM(mAddr)<BR>*mAddr 读的地址位<BR>***************************************<BR>Public Function ReadM(mAddr As String) As Boolean<BR> Dim myHex As String, Q1 As String, Q2 As Integer, Q3 As Integer, QQ As String<BR> Dim myTest As String, Ret As String, S1 As Integer, S2 As Integer<BR> On Error GoTo ReadErr<BR> <BR> select Case Left(mAddr, 1)<BR> Case "M"<BR> Q1 = 256 0100 能读到M1535<BR> Case "S"<BR> Q1 = 0 0000<BR> Case "T"<BR> Q1 = 192 00C0<BR> Case "C"<BR> Q1 = 448 01C0<BR> Case "X"<BR> Q1 = 128 0080<BR> Case "Y"<BR> Q1 = 160 00A0<BR> End Select<BR> <BR> Q2 = Mid(mAddr, 2)<BR> If Q1 = 128 Then<BR> Q2 = OCT_to_DEC(Q2) 八进制转换成十进制<BR> ElseIf Q1 = 160 Then<BR> Q2 = OCT_to_DEC(Q2) 八进制转换成十进制<BR> End If<BR> S1 = Q2 \ 8 取整数<BR> Q3 = Q1 S1<BR> QQ = Hex(Q3)<BR> If Len(QQ) = 3 Then<BR> QQ = "0" & QQ<BR> ElseIf Len(QQ) = 2 Then<BR> QQ = "00" & QQ<BR> ElseIf Len(QQ) = 1 Then<BR> QQ = "000" & QQ<BR> End If<BR> <BR> myHex = "0" & QQ & "01" & Chr(3)<BR> myHex = Chr(2) & myHex & Check_FCS(myHex)<BR> Form1.MSComm.Output = myHex<BR> <BR> Call Sleep(Tim)<BR> myTest = Form1.MSComm.Input<BR> If Len(myTest) < 4 1 Then GoTo ReadErr 查错<BR> If Right(myTest, 2) <> Check_FCS(Mid(myTest, 2, 3)) Then GoTo ReadErr 查错<BR> Ret = "&H" & (Mid(myTest, 2, 2))<BR> Ret = H_To_B(Ret)<BR> S2 = Q2 Mod 8 取余数<BR> S2 = 8 - S2<BR> If Mid(Ret, S2, 1) = 1 Then<BR> ReadM = True<BR> Else<BR> ReadM = False<BR> End If<BR> Exit Function<BR>ReadErr:<BR> Debug.Print "ReadM Error"<BR>End Function<BR> 这样就能实现查询操作了,可以看到X,Y的地址有点儿变化,这是因为X、Y是8进制的所以要进行一下转换,我们来写个函数如下:<BR>
<P align=left><BR>*************************************************************<BR>* 用途:将八进制转化为十进制<BR>* 输入:Oct(八进制数)<BR>* 输入数据类型:String<BR>* 输出:OCT_to_DEC(十进制数)<BR>* 输出数据类型:Long<BR>* 输入的最大数为17777777777,输出的最大数为2147483647<BR>*************************************************************<BR>
<P align=left>Public Function OCT_to_DEC(ByVal Oct As String) As Long<BR> Dim i As Long<BR> Dim B As Long<BR> <BR> For i = 1 To Len(Oct)<BR> select Case Mid(Oct, Len(Oct) - i 1, 1)<BR> Case "0": B = B 8 ^ (i - 1) * 0<BR> Case "1": B = B 8 ^ (i - 1) * 1<BR> Case "2": B = B 8 ^ (i - 1) * 2<BR> Case "3": B = B 8 ^ (i - 1) * 3<BR> Case "4": B = B 8 ^ (i - 1) * 4<BR> Case "5": B = B 8 ^ (i - 1) * 5<BR> Case "6": B = B 8 ^ (i - 1) * 6<BR> Case "7": B = B 8 ^ (i - 1) * 7<BR> End Select<BR> Next i<BR> OCT_to_DEC = B<BR>End Function<BR> 因为要判断读到的8位中到底那个位置的状态就要写个16进制转2进制的函数如下:<BR>
<P align=left>*************************************************************<BR>* 用途:将16进制转化为2进制<BR>* 输入:H_To_B(10进制)<BR>* 输入数据类型:String<BR>* 输出:H_To_B(2进制数)<BR>* 输出数据类型:string<BR>*************************************************************<BR>Public Function H_To_B(ByVal Hex As String) As String<BR>Dim i As Long<BR>Dim B As String<BR>
<P align=left>Hex = UCase(Hex)<BR>For i = 1 To Len(Hex)<BR>Select Case Mid(Hex, i, 1)<BR>Case "0": B = B & "0000"<BR>Case "1": B = B & "0001"<BR>Case "2": B = B & "0010"<BR>Case "3": B = B & "0011"<BR>Case "4": B = B & "0100"<BR>Case "5": B = B & "0101"<BR>Case "6": B = B & "0110"<BR>Case "7": B = B & "0111"<BR>Case "8": B = B & "1000"<BR>Case "9": B = B & "1001"<BR>Case "A": B = B & "1010"<BR>Case "B": B = B & "1011"<BR>Case "C": B = B & "1100"<BR>Case "D": B = B & "1101"<BR>Case "E": B = B & "1110"Case "F": B = B & "1111"<BR>End Select<BR>Next i<BR>While Left(B, 1) = "0" 此三句话为去0<BR>B = Right(B, Len(B) - 1)<BR>Wend<BR>H_To_B = B<BR>End Function<BR>
<P align=left> 这样我们就完成了对bit位的状态查询。<BR>
<P align=left><B></B><BR>
<P align=left><B></B><BR>
<P align=left><B>1</B><B>、DEVICE READ(读出软设备状态值)</B><BR>
<P align=left>计算机向PLC发送:<BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>首地址<BR></P></TD>
<TD>
<P align=center>位数<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>GROUP ADDRESS<BR></P></TD>
<TD>
<P align=center>BYTES<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left>例子:从D123开始读取4个字节数据<BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>30h<BR></P></TD>
<TD>
<P align=center><B><U>31h,30h,46h,36h</U></B><BR></P></TD>
<TD>
<P align=center>30h,34h<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>37h,34h<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left>地址算法:address=address*2 1000h<BR>
<P align=left>再转换成ASCII<BR>
<P align=left>31h,30h,46h,36h<BR>
<P align=left><B>PLC</B><B>返回</B><BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>1ST DATA<BR></P></TD>
<TD>
<P align=center>2ND DATA<BR></P></TD>
<TD>
<P align=center>.....<BR></P></TD>
<TD>
<P align=center>LAST DATA<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left>注:最多可以读取64个字节的数据<BR>
<P align=left>例子:从指定的存储器单元读到3584这个数据<BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center><B><I>33h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>35h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>38h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>34h</I></B><BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>44h,36h<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left> <BR>
<P align=left><B>2</B><B>、DEVICE WRITE(向PLC软设备写入值)</B><BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width=689>
<TBODY>
<TR>
<TD width=33>
<P align=center>始<BR></P></TD>
<TD width=41>
<P align=center>命令<BR></P></TD>
<TD width=134>
<P align=center>首地址<BR></P></TD>
<TD width=52>
<P align=center>位数<BR></P></TD>
<TD width=288 colSpan=4>
<P align=center>数据<BR></P></TD>
<TD width=46>
<P align=center>终<BR></P></TD>
<TD width=49>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD width=33>
<P align=center>STX<BR></P></TD>
<TD width=41>
<P align=center>CMD<BR></P></TD>
<TD width=134>
<P align=center>GROUP ADDRESS<BR></P></TD>
<TD width=52>
<P align=center>BYTES<BR></P></TD>
<TD width=80>
<P align=center>1ST DATA<BR></P></TD>
<TD width=85>
<P align=center>2ND DATA<BR></P></TD>
<TD width=26>
<P align=center>......<BR></P></TD>
<TD width=79>
<P align=center>LAST DATA<BR></P></TD>
<TD width=46>
<P align=center>ETX<BR></P></TD>
<TD width=49>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD width=37>
<P align=left><BR> </P></TD>
<TD width=44>
<P align=left><BR> </P></TD>
<TD width=139>
<P align=left><BR> </P></TD>
<TD width=55>
<P align=left><BR> </P></TD>
<TD width=84>
<P align=left><BR> </P></TD>
<TD width=89>
<P align=left><BR> </P></TD>
<TD width=43>
<P align=left><BR> </P></TD>
<TD width=95>
<P align=left><BR> </P></TD>
<TD width=49>
<P align=left><BR> </P></TD>
<TD width=53>
<P align=left><BR> </P></TD></TR></TBODY></TABLE></DIV>
<P align=left>例子:向D123开始的两个存储器中写入1234,ABCD<BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>31h<BR></P></TD>
<TD>
<P align=center><B><U>31h,30h,46h,36h</U></B><BR></P></TD>
<TD>
<P align=center><B><I>30h,34h</I></B><BR></P></TD>
<TD>
<P align=center><B><I>33h,34h,31h,32h,43h,44h,41h,42h</I></B><BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>34h,39h<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left><B>PLC</B><B>返回</B><BR>
<P align=left>ACK (06H) 接受正确<BR>
<P align=left>NAK (15H) 接受错误<BR>
<P align=left> <BR>
<P align=left><B>3</B><B>、位设备强制置位/复位</B><BR>
<P align=left><B>FORCE ON</B><B>置位</B><BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>地址<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>ADDRESS<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>37h<BR></P></TD>
<TD>
<P align=center>address<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>sum<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left><B>FORCE OFF</B><B>复位</B><BR>
<DIV align=center>
<TABLE border=1 cellPadding=0 width="90%">
<TBODY>
<TR>
<TD>
<P align=center>始<BR></P></TD>
<TD>
<P align=center>命令<BR></P></TD>
<TD>
<P align=center>地址<BR></P></TD>
<TD>
<P align=center>终<BR></P></TD>
<TD>
<P align=center>和校验<BR></P></TD></TR>
<TR>
<TD>
<P align=center>STX<BR></P></TD>
<TD>
<P align=center>CMD<BR></P></TD>
<TD>
<P align=center>ADDRESS<BR></P></TD>
<TD>
<P align=center>ETX<BR></P></TD>
<TD>
<P align=center>SUM<BR></P></TD></TR>
<TR>
<TD>
<P align=center>02h<BR></P></TD>
<TD>
<P align=center>38h<BR></P></TD>
<TD>
<P align=center>address<BR></P></TD>
<TD>
<P align=center>03h<BR></P></TD>
<TD>
<P align=center>sum<BR></P></TD></TR></TBODY></TABLE></DIV>
<P align=left> <BR>
<P align=left><B>PLC</B><B>返回</B><BR>
<P align=left>ACK(06H) 接受正确<BR>
<P align=left>NAK(15H) 接受错误<BR>
<P align=left> <BR>
<P align=left>设备强制中的地址公式:Address=Address * 100h (*) (必须为4位,不足4位前面补0)<BR>
<P align=left> 注:*号所代表值:<BR>
<P align=left> C:14 M:8 T:6 Y:5 X:4 S:0<BR>
<P align=left>如对M2置位,则为地址为:2*256(100H) 8=0520 转为十六进制为:0208 再换为ASCII就是: 30 32 30 38<BR>
<P align=left> <BR>
<P align=left><B>说明:</B><BR>
<P align=left>1.帧中的BYTES表示需要读取或者写入的字节数。<BR>
<P align=left>2.地址算法上有说明。<BR>
<P align=left>3.累加和是从STX后面一个字节开始累加到ETX的和。<BR>
<P align=center><B>VB</B><B>读写三菱FX系列PLC数据示例</B><BR>
<P align=left> 通过前面两篇文章,我们了解了三菱FX系列PLC的编程口通信协议。为了更方便读者学习这里提供一个用VB编写的示例,其中包含一个通用模块,如果你需要对此类PLC进行读写数据区的操作可以下载看看。<BR><BR> 三菱FX系列PLC的校验采用的是和校验,在写数据和读数据时都会有这个和校验,和校验用于检查数据包是否有错。因此我们必需知道和校验的算法,才能成功地进行通信。在这里我们提供了一个和校验的VB源代码: <BR>
<P align=left> <BR>
<P align=left>***************************************************<BR>Private Function Check_FCS(mStr As String) As String<BR> Dim K As Integer, mTest As Integer<BR> For K = 1 To Len(mStr)<BR> mTest = mTest Asc(Mid(mStr, K, 1))<BR> Next<BR> Check_FCS = Right(Hex(mTest), 2)<BR>End Function<BR>***************************************************<BR> <BR>函数中的 mStr 参数为命令、位数、数据、终止符的合集,读取时不包括数据。使用Check_FCS函数时将返回和校验码。<BR><BR> 当向PLC写数据时,数据必须为四位的十六进制数,并且低位在前、高位在后。这里提供一个循环代码,将一个数组里的整数转换为向PLC写入数据的字符串:<BR> <BR>For Q = 0 To UBound(mData)<BR> myHex = myHex & Format(Hex(mData(Q) Mod &HFF), "00") & Format(Hex(mData(Q) \ &HFF), "00")<BR>Next<BR> <BR>
<P align=left> 在上面的代码中 mData 为向PLC写入数据的数组,myHex 最终得到的就是写入PLC时所要的数据字符串。同样的从PLC读出来的数据也是四位的十六进制,低位在前、高位在后。所以我们有必要将其转换为对应的整数。其主要的转换代码如下:<BR> <BR>
<P align=left>ReDim mData(mCounts - 1)<BR>For Q = 0 To mCounts - 1<BR> mData(Q) = "&H" & (Mid(myHex, 4 Q * 4, 2) & Mid(myHex, 2 Q * 4, 2))<BR>Next <BR>
<P align=left> <BR>mCounts 为要读取数据区的个数,myHex 是串口返回来的数据,经过上面计算返回的整数值将排列在 mData 数组里。<BR>
<P align=left><BR><BR>Option Explicit<BR>Dim outdata() As Byte 定义发送数组,用来存放转换后的命令数据<BR>Dim Rcvlen As Integer 定义接收到的数据的长度<BR>Dim Rcv() As Byte 定义接收数组,用来存放接收到的数据<BR>Dim inString As String 定义输入命令字符串<BR>Dim RcvFinFlag As Boolean 定义接收完成标志<BR>Dim ReadFlag As Boolean 定义“读命令”标志<BR>Dim ReSendFlag As Boolean<BR>Dim CheckFlag As Boolean<BR>Dim No1 As Integer 定义重发次数设定<BR>Dim No2 As Integer 定义重发次数计数器<BR>Dim FinalDataLen As Integer 定义接收到数据的最终长度变量<BR>Dim SaveString As String 定义输入命令暂存字符串变量<BR><BR>Private Sub Cmdopen_Click()<BR> If Not MSComm1.PortOpen Then MSComm1.PortOpen = True 如果串口没有打开,打开串口<BR> inString = "00FFWWAD00100202A11111" 输入开机命令字符串<BR> Call send(inString) 调用发送子程序,形成命令帧并发送<BR> Timer1.Enabled = True 打开“定时读取温度值”定时器<BR>End Sub<BR><BR>Private Sub Cmdset_Click()<BR> inString = "00FFWWAD0010020251" 输入命令报文的固定部分<BR> Dim temp As Variant<BR> Dim t As Variant<BR> Dim j As Integer<BR> temp = Trim(Txtset.Text) 取设定文本框输入的数据,以字符串的形式处理<BR> If Not IsNumeric(temp) Then 如果输入的数据有格式错误<BR> MsgBox "请按如下范围和格式输入数据:-100.0~300.0", vbExclamation, "输入数据范围错误"<BR> Exit Sub<BR> End If<BR> Dim Temperature As Integer<BR> If temp > 300 Or temp < -100 Then 如果超出规定范围之内<BR> MsgBox "请输入-100.0~300.0之间的数", vbExclamation, "输入数据格式错误"<BR> Exit Sub<BR> Else<BR> Temperature = CInt(10 * Val(temp)) 将输入的数据转换为以0.1度为单位的整数<BR> t = Hex(Temperature) 将输入的温度值转换为十六进制数<BR> j = Len(t) 求转换后的十六进制数的位数<BR> t = String(4 - j, "0") & t 十六进制数高位添0,处理为4位的格式<BR> End If<BR> ReadFlag = False 复位读标志<BR> inString = inString & t 转换后的温度设定值附加到instring的末尾<BR> If Timer1.Enabled = True Then 若在开机状态下<BR> Timer1.Enabled = False 发送温度设置命令前,暂时关闭定时器1<BR> Call send(inString) 发送设定值到PLC<BR> Timer1.Enabled = True 发送温度设置命令后,重新开始定时器1<BR> Timer1.Enabled = True 若在关机状态下<BR> Else<BR> Call send(inString) 直接发送设定值到PLC<BR> End If<BR>End Sub<BR><BR>Private Sub Cmdstop_Click()<BR> MSComm1.PortOpen = False<BR> End<BR>End Sub<BR><BR>初始化<BR>Private Sub Form_Load()<BR> With MSComm1<BR> .CommPort = 1 选择串口1<BR> .Settings = "9600,n,8,1" 设置通讯格式<BR> .InputMode = comInputModeBinary 以二进制格式读取缓冲区<BR> .RThreshold = 1 接收到的字符数大于等于1时产生接收事件<BR> .InputLen = 0 读出接收缓冲区所有的内容<BR> .OutBufferCount = 0 清空发送缓冲区<BR> .InBufferCount = 0 清空接收缓冲区<BR> End With<BR> If Not MSComm1.PortOpen = True Then MSComm1.PortOpen = True 打开串口1<BR> Timer1.Interval = 4000 设置定时读取温度值的中断时间<BR> Timer1.Enabled = False 初始化定时读取温度值定时器<BR> Timer2.Interval = 1000 设置超时判定定时器的中断时间<BR> Timer2.Enabled = False 初始化超时判断定时器<BR> No1 = 3 初始化重新发送次数3次<BR> No2 = 0 初始化重发次数为0<BR> RcvFinFlag = True 初始化接收完成标志<BR> ReSendFlag = True 初始化重发标志<BR> End Sub<BR><BR>Private Sub MSComm1_OnComm()<BR> Dim RcvTemp() As Byte 定义存放每次接收到的数据的暂存数组<BR> Dim i As Integer<BR> Dim t As Variant<BR> ReDim Preserve Rcv(100) As Byte 预设接收字符数组Rcv<BR> If RcvFinFlag Then 如果报文接收处理完成<BR> Exit Sub<BR> Else<BR> Select Case MSComm1.CommEvent MSComm控件产生通讯事件或通讯错误<BR> Case comEventFrame 检测到一个因双方的通讯格式不同引发的错误<BR> MsgBox "双方通讯格式不一致", vbExclamation, "提示" 弹出错误<BR> Timer1.Enabled = False 关闭定时发送定时器<BR> Timer2.Enabled = False 关闭超时判断定时器<BR> Exit Sub<BR> Case comEvReceive 若接收到字符<BR> RcvTemp = MSComm1.Input 将接收缓冲区的内容送入暂存数组<BR> For i = LBound(RcvTemp) To UBound(RcvTemp) <BR> Rcvlen = Rcvlen 1 接收字符个数加1,Rcvlen的初始值为-1<BR> If Rcvlen > 100 Then 如果接收数据超过接收数组上限<BR> Rcvlen = -1 复位接收到的数据的长度变量<BR> Call ErrorHandle 进行错误处理<BR> Exit Sub<BR> End If<BR> Rcv(Rcvlen) = RcvTemp(i) 将接收到的各字节送入接收字节数组<BR> <BR> Next<BR> ReDim Preserve Rcv(Rcvlen) As Byte 重新定义并保存接收字符数组<BR> If Rcvlen >= 1 Then<BR> For i = LBound(Rcv) 1 To UBound(Rcv)<BR> If Rcv(i) = &HA And Rcv(i - 1) = &HD Then 如果接收到回车换行符<BR> RcvFinFlag = True 报文接收完成标志置位<BR> FinalDataLen = i 保存接收到的最终数据长度<BR> ReDim Preserve Rcv(FinalDataLen) As Byte 重新定义并保存接收字符数组<BR> Rcvlen = -1 初始化接收到的数据的长度变量<BR> Exit For<BR> End If<BR> Next<BR> End If<BR> End Select<BR> End If<BR> If RcvFinFlag = True Then 若报文接收结束<BR> If ReadFlag Then 若为定时读取数据命令<BR> If Rcv(0) = &H2 Then 且报文以STX开始<BR> t = RcvDataChk(Rcv) 调用接收数据检查子程序<BR> If t Then 若接收到的数据正确<BR> Call RcvDataDisplay(Rcv, FinalDataLen) 显示<BR> Call confirm(&H6) 向 PLC发送ACK开始的确认报文<BR> ReadFlag = False “读命令”复位标志<BR> No2 = 0 重发计数次数复位<BR> Timer2.Enabled = False 关闭通讯超时定时器<BR> Else<BR> Call confirm(&H15) 向PLC发送NAK开始的无法确认报文<BR> Call ErrorHandle 进行错误处理<BR> End If<BR> Else<BR> If Rcv(0) = &H15 Then Call confirm(&H15) <BR> Call ErrorHandle <BR> End If<BR> Else<BR> If Rcv(0) = &H6 And FinalDataLen = 6 Then 若PLC正确执行写命令<BR> Timer2.Enabled = False 关闭通讯超时定时器<BR> No2 = 0 复位重发次数<BR> Exit Sub<BR> Else<BR> Call ErrorHandle <BR> End If<BR> End If<BR> End If<BR>End Sub<BR><BR>Private Sub Timer1_Timer()<BR> ReadFlag = True 置位“读命令”标志<BR> inString = "00FFWRAD010001" 输入定时读取D100的命令字符串<BR> Call send(inString) 调用发送子程序,形成命令帧发送<BR>End Sub<BR><BR>Private Sub Timer2_Timer()<BR> Call ErrorHandle<BR>End Sub<BR><BR><BR>Private Sub send(inString As String)<BR> Dim length, i As Integer<BR>If RcvFinFlag = True Then 前以命令执行完毕,接收完成标志为True<BR> SaveString = inString 保存命令字符串<BR> Rcvlen = -1 接收数据存放数组的下标初始化<BR> RcvFinFlag = False 接收完成标志复位<BR> length = Len(inString) 求形参传递过来的字符串的长度<BR> ReDim Preserve outdata(0 To length) As Byte 重新定义发送数据数组,其元素个数为length 1<BR> outdata(0) = &H5 命令报文以控制代码“ENQ”开始<BR> For i = 1 To length 字符串转换为ASCII码,送入发送数组<BR> outdata(i) = Asc(Mid(inString, i, 1)) <BR> Next<BR> Call FCScheck(outdata) 产生校验和,形成发送帧<BR> length = UBound(outdata) <BR> ReDim Preserve outdata(0 To length 2) As Byte 重新定义发送数据数组<BR> outdata(length 1) = &HD 因为是传输格式4,最后添加回车换行符<BR> outdata(length 2) = &HA <BR> MSComm1.Output = outdata 发送命令帧<BR> Timer2.Enabled = True 开启超时判断定时器<BR>Else<BR> MsgBox "前以命令尚未执行完毕", vbExclamation, "操作提示"<BR>End If<BR>End Sub<BR><BR>Private Sub FCScheck(outdata() As Byte)<BR> Dim BufLen As Integer, Buf As String 定义字符串长度变量和字符串变量<BR> Dim i As Integer<BR> Dim CheckSum As Long 定义校验和变量<BR> BufLen = UBound(outdata) 求outdata数组可用的最大下标<BR> CheckSum = 0 初始化校验和<BR> For i = LBound(outdata) 1 To UBound(outdata) 求和时不包括开始的控制代码<BR> CheckSum = (CheckSum outdata(i)) And &HFF 对outdata数组的元素求和,只保留低位字节<BR> Next i<BR> Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum))<BR> 若校验和只有1位,则高位添零,补足位2位<BR> ReDim Preserve outdata(BufLen 2) As Byte<BR> outdata(BufLen 1) = Asc(Mid(Buf, 1, 1)) 校验和转换为ASCII码,低位在前<BR> outdata(BufLen 2) = Asc(Mid(Buf, 2, 1))<BR>End Sub<BR><BR>Private Sub ErrorHandle()<BR> If No2 >= 0 And No2 < No1 Then 若重发次数小于重发设定值,则重发命令<BR> No2 = No2 1 重发次数加1<BR> RcvFinFlag = True 置位接收完成标志,准备重发<BR> Call send(SaveString) 重发<BR> Exit Sub<BR> Else<BR> Timer1.Enabled = False 关闭定时读取温度值定时器<BR> Timer2.Enabled = False 关闭超时判断定时器<BR> MsgBox "请检查硬件连接及报文设置", vbExclamation, "通讯超时或通讯过程出错"<BR> No2 = 0 复位重发次数<BR> ReadFlag = False 读命令标志复位<BR> RcvFinFlag = True 接收完成标志置位<BR> Exit Sub<BR> End If<BR>End Sub<BR><BR>Private Sub txtset_keypress(KeyAscii As Integer)<BR> Dim temp As String<BR> temp = (Chr(KeyAscii))<BR> If Not (temp Like "[0-9;.;-]" Or KeyAscii = 8 Or KeyAscii = 13) Then<BR> MsgBox "只允许输入0~9、小数点、负号或退格和火车", vbExclamation, "键盘输入字符错误"<BR> Exit Sub<BR> End If<BR>End Sub<BR><BR>Private Function RcvDataChk(Cdata() As Byte) As Boolean<BR> Dim i As Integer<BR> Dim EndNo As Integer<BR> CheckFlag = False 校验标志初始化<BR> For i = 0 To UBound(Cdata)<BR> If Cdata(i) = &H3 Then 如果找到接收到的报文中的ETX(文本结束符)<BR> EndNo = i 保存ETX所在的数组元素的下标<BR> Exit For<BR> End If<BR> Next<BR> Dim Ddata() As Byte 定义新数组<BR> ReDim Ddata(EndNo) As Byte<BR> For i = 0 To EndNo 将接收到的报文中需要求和的部分存入该数组<BR> Ddata(i) = Cdata(i)<BR> Next<BR> Call FCScheck(Ddata) 调用求校验和子程序。校验和存放在数组末尾<BR> If Ddata(EndNo 1) = Cdata(EndNo 1) And Ddata(EndNo 2) = Cdata(EndNo 2) Then<BR> CheckFlag = True 如果求校验和与接收到的相等,将校验标志置位<BR> End If<BR> RcvDataChk = CheckFlag 返回校验结果<BR>End Function<BR><BR>Private Sub RcvDataDisplay(Rcv() As Byte, Rcvlen As Integer)<BR> If Rcv(0) = &H2 Then 如果是发送读命令后PLC返回的报文<BR> Dim Shwtemp As Variant 定义用于显示字符串的变量<BR> Dim i As Integer<BR> Shwtemp = "" 显示字符串变量初始化<BR> For i = 5 To Rcvlen I=5开始上传的数据<BR> If Rcv(i) <> &H3 Then 如果不是文本结束的代码ETX<BR> Shwtemp = Shwtemp & Chr(Rcv(i)) 接收到的字符送入显示字符串变量<BR> Else<BR> Exit For<BR> End If<BR> Next<BR> Txtview = Format((Val("&h" & Shwtemp) / 10), "# #0.0") 按小数点后一位的格式显示温度值<BR> End If<BR>End Sub<BR>读命令接收数据响应子程序<BR>Private Sub confirm(CodeByte As Byte)<BR> ReDim outdata(6) As Byte<BR> outdata(0) = CodeByte 报文以控制代码ACK或NAK开始<BR> outdata(1) = &H30 PLC的站号设置为0<BR> outdata(2) = &H30<BR> outdata(3) = &H46 FX系列PLC的标识FFH<BR> outdata(4) = &H46<BR> outdata(5) = &HD 因为送传送格式4,以回车换行符结束<BR> outdata(6) = &HA<BR> MSComm1.Output = outdata 发送确认或不能确认的报文<BR>End Sub<BR></P> |
|