一网友需要采集接收从PLC发过来的48路采集数据,特为其编写了一个小程序,其通信协议如下所示:
返回固定地址adr 00 43 40 41(固定指令) 返回: adr 43 CRC 读未校准电压读数寄存器 adr 03 0100 0030 CRC 返回: adr 03 60 xxxx xxxx ..... xxxx CRC, xxxx是48个电压,放大100倍. 读校准后电压寄存器 adr 03 0130 0030 CRC 返回: adr 03 60 xxxx xxxx ..... xxxx CRC, xxxx是48个电压,放大100倍. 读5V时,ADC电压采样值 adr 03 0300 0030 CRC 返回: adr 03 60 xxxx xxxx ..... xxxx CRC, xxxx是48个输入电压为5V时各点未校准值,放大100倍. 读48V时,ADC电压采样值 adr 03 0330 0030 CRC 返回: adr 03 60 xxxx xxxx ..... xxxx CRC, xxxx是48个输入电压为48V时各点未校准值, 放大100倍. 读基准电压 adr 03 0460 0002 CRC 返回: adr 03 04 xxxx xxxx CRC, 第一个值为5V基准,第二个值为48V基准,放大100倍. 设定老化机种电压参数寄存器 adr 10 0380 0001 02 XXXX CRC 第一通道校准 写某路 X1点校准寄存器 adr 10 0200 0001 02 XXXX CRC (第1个点 5V) 返回: adr 10 0200 0002 CRC //0200 表示第一通道,0201 表示第二通道...022f表示48通道 写某路 X2点校准寄存器 adr 10 0240 0001 02 XXXX CRC (第2个点 48V) 返回: adr 10 0240 0002 CRC////0240 表示第一通道,0241 表示第二通道...026f表示48通道 校准方法: 1: 将标准电压X1(比如5V)接入。等待几秒钟以便读数稳定。 2: 发命令"adr 10 0200 0001 02 uuuu CRC",记录X1点校准, uuuu是标准电压X1(放大100倍,即两位小数)。 1: 将标准电压X2(比如48V)接入。等待几秒钟以便读数稳定。 2: 发命令"adr 10 0200 0001 02 uuuu CRC",记录X2点校准, uuuu是标准电压X2(放大100倍,即两位小数)。 校准结束。
代码如下所示:
Dim bRev() As Byte Dim bSnd() As Byte '发送数据 Private Sub cmdCom_Click() On Error GoTo err1 If cmdCom.Caption = "打开端口" Then '设置串口 On errr GoTo defaults If Cobcom.Text = "" Then MsgBox "请选择串口号!" Exit Sub End If If cobspeed.Text = "" Then MsgBox "请选择波特率!" Exit Sub End If MSComm.CommPort = Cobcom.Text '选用com串行口"9600,N,8,1" MSComm.Settings = cobspeed.Text & ",N,8,1" MSComm.PortOpen = True '打开通信 cmdCom.Caption = "关闭端口" Exit Sub defaults: MSComm1.CommPort = 1 MSComm1.Settings = "9600,N,8,1" MsgBox "设置错误!" & Chr(13) & "自动设置默认值:9600,N,8,1" Else cmdCom.Caption = "打开端口" MSComm.PortOpen = False '关闭通信 End If Exit Sub err1: MsgBox "端口已打开!" End Sub Private Sub cmdExit_Click() If MSComm.PortOpen Then MsgBox "串口还在接收处理数据,请关闭后在退出!" Else End End If End Sub Private Sub cmdgetaddr_Click() ReDim bSnd(0 To 3) As Byte bSnd(0) = &H0 bSnd(1) = &H43 bSnd(0) = &H40 bSnd(1) = &H41 If (MSComm.PortOpen) Then MSComm.Output = bSnd Else MsgBox "串口没有打开,请打开串口!" End If End Sub Private Sub cmdJiao_Click() ReDim bSnd(0 To 10) As Byte If txtV.Text = "" Then MsgBox "请填写校准电压值!" Exit Sub End If If cobVol.Text = "" Then MsgBox "请选择基准电压!" Exit Sub End If If txtChl.Text = "" Then MsgBox "请填写通道号!" Exit Sub End If bSnd(0) = Int(Val(addr.Text)) bSnd(1) = &H10 '10 bSnd(2) = &H2 '02 If cobVol.Text = "5V" Then bSnd(3) = &H0 + Int(Val(txtChl.Text)) Else bSnd(3) = &H40 + Int(Val(txtChl.Text)) End If bSnd(4) = &H0 '00 bSnd(5) = &H1 '01 bSnd(6) = &H2 '02 '处理电压 Dim v As Integer v = Int(Val(txtV.Text) * 100) '获取电压值 bSnd(7) = (v / 256) bSnd(8) = v Mod 256 Dim sTmp(0 To 8) As Byte For i = 0 To 8 sTmp(i) = bSnd(i) Next bSnd(6) = CRC16INT(sTmp, "L") bSnd(7) = CRC16INT(sTmp, "H") If MSComm.PortOpen Then MSComm.Output = bSnd End If End Sub Private Sub cmdRun_Click() If cmdRun.Caption = "运行" Then If zhouqi.Text = "" Then MsgBox "请设置好周期在运行程序!" Exit Sub End If If Val(zhouqi.Text) > 0 Then timRead.Interval = Int(Val(zhouqi.Text)) timRead.Enabled = True cmdRun.Caption = "停止" Else MsgBox "周期设置错误!" End If Else timRead.Enabled = False cmdRun.Caption = "运行" End If End Sub Private Sub Form_Load() For i = 1 To 16 On Error Resume Next '当运行发生错误时,控件转到紧接着发生错误的语句之后的语句,并在此继续运行 MSComm.CommPort = i MSComm.PortOpen = True Select Case Err.Number Case 0 '错误号为0(也就是没出错), Cobcom.AddItem i MSComm.PortOpen = False Case 8005 '错误号为8005,也就是端口被占用 Cobcom.AddItem i MSComm.PortOpen = False End Select Err = 0 '将错误号置0. 注:Err.Number可以简写为Err ,2者等效 Next If Cobcom.ListCount > 0 Then Cobcom.ListIndex = 0 End If cobVol.ListIndex = 0 cobspeed.AddItem ("4800") cobspeed.AddItem ("9600") cobspeed.AddItem ("115200") Call InitCom cobspeed.Text = "9600" End Sub Private Sub InitCom() MSComm.Settings = "9600,N,8,1" '波特率9600,无奇偶校验位,8位数据位1位停止位 MSComm.InputLen = 0 'input将读取接收缓冲区的全部内容 MSComm.InBufferSize = 1024 '设置接收缓冲区的字节长度 'MSComm1.PortOpen = True '打开通信口 MSComm.InBufferCount = 0 '清除接收缓冲区数据 MSComm.OutBufferCount = 0 '清除发送缓冲区数据 MSComm.InputMode = comInputModeBinary 'comInputModeText 'MSComm.InputMode = comInputModeBinary 'periodic.inteval = 100 '设置ls定时间隔,使遥测命令每隔ls发送1次 MSComm.RThreshold = 1 End Sub Private Sub MSComm_OnComm() Dim i As Integer, SBUF() As Byte If MSComm.CommEvent = comEvReceive Then SBUF = MSComm.Input For i = 0 To UBound(SBUF) Text1.Text = Text1.Text & " " & Hex(SBUF(i)) Next Call DealData(SBUF) End If End Sub Private Sub DealData(sRev() As Byte) Dim sTmp() As Byte Dim i As Integer Dim crcvalue, crcGet Select Case UBound(sRev) Case 3 '返回的是地址,或者是错误数据,验证一下 If (sRev(1) = &H43) Then ReDim sTmp(0 To 1) As Byte sTmp(0) = sRev(0) sTmp(1) = sRev(1) crcvalue = GETCRC16(sTmp, "L") '协议中固定为低位在前,高位在后 crcGet = Hex(sRev(2)) + " " + Hex(sRev(3)) If crcvalue = crcGet Then addr.Text = sTmp(0) txtShowMsg.Text = txtShowMsg.Text + "获取设备地址成功!" + vbCrLf End If End If Case 100 '正常数据电压采样值 '判断地址是否相等,不相等就认为不是所要的数据 If (sRev(0) + "" = addr.Text) And sRev(1) = &H3 Then ReDim sTmp(0 To 98) As Byte For i = 0 To 98 sTmp(i) = sRev(i) Next crcvalue = GETCRC16(sTmp, "L") ' crcGet = Hex(sRev(99)) + " " + Hex(sRev(100)) If crcvalue = crcGet Then For i = 0 To 47 txtChannel(i).Text = (sRev(3 + i) * 256 + sRev(4 + i)) / 100 '把值显示在文本框 Next End If End If Case 7 '读基准电压,写校准等 If (sRev(0) + "" = addr.Text) And sRev(1) = &H10 Then ReDim sTmp(0 To 6) As Byte For i = 0 To 6 sTmp(i) = sRev(i) Next crcvalue = GETCRC16(sTmp, "L") ' crcGet = Hex(sRev(7)) + " " + Hex(sRev(8)) If crcvalue = crcGet Then If (sRev(3) - &H40 > 0) Then txtShowMsg.Text = txtShowMsg.Text + "X2点" + (sRev(3) - &H40 + 1) + "通道校准成功!" + vbCrLf Else txtShowMsg.Text = txtShowMsg.Text + "X1点" + (sRev(3) + 1) + "通道校准成功!" + vbCrLf End If End If End If If (sRev(0) + "" = addr.Text) And sRev(1) = &H3 Then ReDim sTmp(0 To 6) As Byte For i = 0 To 6 sTmp(i) = sRev(i) Next crcvalue = GETCRC16(sTmp, "L") ' crcGet = Hex(sRev(7)) + " " + Hex(sRev(8)) If crcvalue = crcGet Then txtShowMsg.Text = txtShowMsg.Text + "5V基准值:" + sRev(3) + "." + sRev(4) + vbCrLf txtShowMsg.Text = txtShowMsg.Text + "48V基准值:" + sRev(5) + "." + sRev(6) + vbCrLf End If End If End Select End Sub Private Sub timRead_Timer() '发送数据示例 '发送 读校准后电压寄存器 指令 ReDim bSnd(0 To 7) As Byte bSnd(0) = Int(Val(addr.Text)) bSnd(1) = &H3 '03 bSnd(2) = &H1 '01 bSnd(3) = &H0 '00 bSnd(4) = &H0 '00 bSnd(5) = &H30 '30 Dim sTmp(0 To 5) As Byte For i = 0 To 5 sTmp(i) = bSnd(i) Next bSnd(6) = CRC16INT(sTmp, "L") bSnd(7) = CRC16INT(sTmp, "H") If MSComm.PortOpen Then MSComm.Output = bSnd End If End Sub
公共的CRC代码如下所示:
Public Function CRC(str1 As String, HL As String) Dim CRCREG As Long Dim MVAL As Long Dim R As Integer Dim T As Integer CRCREG = 65535 For R = 1 To Len(str1) Step 2 MVAL = Val("&H" + Mid(str1, R, 2)) CRCREG = CRCREG Xor MVAL CRCREG = CRCREG And 65535 For T = 1 To 8 Step 1 If (CRCREG And &H1) Then CRCREG = (CRCREG 2) Xor &HA001 CRCREG = CRCREG And 65535 Else CRCREG = CRCREG 2 CRCREG = CRCREG And 65535 End If Next Next Dim a As Long Dim b As Long a = CRCREG And 255 b = CRCREG And 65280 a = a * 256 b = b / 256 If (a + b) < 16 Then CRC = "000" + Hex(a + b) ElseIf (a + b) < 256 Then CRC = "00" + Hex(a + b) ElseIf (a + b) < 4096 Then CRC = "0" + Hex(a + b) Else CRC = Hex(a + b) End If If HL = "H" Then CRC = Left(CRC, 2) ElseIf HL = "L" Then CRC = Right(CRC, 2) End If End Function Function CRC16(data() As Byte, HL As String) As String Dim CRC16Lo As Byte Dim CRC16Hi As Byte Dim CL As Byte Dim SaveHi As Byte Dim SaveLo As Byte Dim ii As Integer Dim flag As Integer CRC16Lo = &HFF CRC16Hi = &HFF CL = &H1 CH = &HA0 For ii = 0 To UBound(data) CRC16Lo = CRC16Lo Xor data(ii) For flag = 0 To 7 SaveHi = CRC16Hi SaveLo = CRC16Lo CRC16Hi = CRC16Hi 2 CRC16Lo = CRC16Lo 2 If ((SaveHi And &H1) = &H1) Then CRC16Lo = CRC16Lo Or &H80 End If If ((SaveLo And &H1) = &H1) Then CRC16Hi = CRC16Hi Xor CH CRC16Lo = CRC16Lo Xor CL End If Next flag Next ii Dim ReturnData(1) As Byte ReturnData(0) = CRC16Hi ReturnData(1) = CRC16Lo If HL = "H" Then CRC16 = Hex(ReturnData(0)) End If If HL = "L" Then CRC16 = Hex(ReturnData(1)) End If End Function ' 用途:将十六进制转化为十进制 ' 输入:Hex(十六进制数) ' 输入数据类型:String ' 输出:H2D(十进制数) ' 输出数据类型:Long ' 输入的最大数为7FFFFFFF,输出的最大数为2147483647 Public Function H2D(ByVal Hex As String) As Long Dim i As Long Dim b As Long Hex = UCase(Hex) For i = 1 To Len(Hex) Select Case Mid(Hex, Len(Hex) - i + 1, 1) Case "0": b = b + 16 ^ (i - 1) * 0 Case "1": b = b + 16 ^ (i - 1) * 1 Case "2": b = b + 16 ^ (i - 1) * 2 Case "3": b = b + 16 ^ (i - 1) * 3 Case "4": b = b + 16 ^ (i - 1) * 4 Case "5": b = b + 16 ^ (i - 1) * 5 Case "6": b = b + 16 ^ (i - 1) * 6 Case "7": b = b + 16 ^ (i - 1) * 7 Case "8": b = b + 16 ^ (i - 1) * 8 Case "9": b = b + 16 ^ (i - 1) * 9 Case "A": b = b + 16 ^ (i - 1) * 10 Case "B": b = b + 16 ^ (i - 1) * 11 Case "C": b = b + 16 ^ (i - 1) * 12 Case "D": b = b + 16 ^ (i - 1) * 13 Case "E": b = b + 16 ^ (i - 1) * 14 Case "F": b = b + 16 ^ (i - 1) * 15 End Select Next i H2D = b End Function Function GETCRC16(data() As Byte, HLBegin As String) As String Dim CRC16Lo As Byte Dim CRC16Hi As Byte Dim CL As Byte Dim SaveHi As Byte Dim SaveLo As Byte Dim ii As Integer Dim flag As Integer CRC16Lo = &HFF CRC16Hi = &HFF CL = &H1 CH = &HA0 For ii = 0 To UBound(data) CRC16Lo = CRC16Lo Xor data(ii) For flag = 0 To 7 SaveHi = CRC16Hi SaveLo = CRC16Lo CRC16Hi = CRC16Hi 2 CRC16Lo = CRC16Lo 2 If ((SaveHi And &H1) = &H1) Then CRC16Lo = CRC16Lo Or &H80 End If If ((SaveLo And &H1) = &H1) Then CRC16Hi = CRC16Hi Xor CH CRC16Lo = CRC16Lo Xor CL End If Next flag Next ii Dim ReturnData(1) As Byte ReturnData(0) = CRC16Hi ReturnData(1) = CRC16Lo 'H表示高位在前,L表示低位在前 If HLBegin = "H" Then GETCRC16 = Hex(ReturnData(0)) + " " + Hex(ReturnData(1)) End If If HLBegin = "L" Then GETCRC16 = Hex(ReturnData(1)) + " " + Hex(ReturnData(0)) End If End Function Function CRC16INT(data() As Byte, HL As String) As Integer Dim CRC16Lo As Byte Dim CRC16Hi As Byte Dim CL As Byte Dim SaveHi As Byte Dim SaveLo As Byte Dim ii As Integer Dim flag As Integer CRC16Lo = &HFF CRC16Hi = &HFF CL = &H1 CH = &HA0 For ii = 0 To UBound(data) CRC16Lo = CRC16Lo Xor data(ii) For flag = 0 To 7 SaveHi = CRC16Hi SaveLo = CRC16Lo CRC16Hi = CRC16Hi 2 CRC16Lo = CRC16Lo 2 If ((SaveHi And &H1) = &H1) Then CRC16Lo = CRC16Lo Or &H80 End If If ((SaveLo And &H1) = &H1) Then CRC16Hi = CRC16Hi Xor CH CRC16Lo = CRC16Lo Xor CL End If Next flag Next ii Dim ReturnData(1) As Byte ReturnData(0) = CRC16Hi ReturnData(1) = CRC16Lo If HL = "H" Then CRC16INT = ReturnData(0) End If If HL = "L" Then CRC16INT = ReturnData(1) End If End Function
这样完整的代码就弄好了。
运行如下所示: