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