我们最常用的是在RS-485上使用MODBUS协议。其中最重要指令是读写寄存器,介绍如下:
03功能码 功能:读保持寄存器
请求
地址(1 个字节)+ 功能码(0x03)+起始地址(2 个字节)+寄存器数量(2 个字节)+校验码(2 个字节)
应答
地址(1 个字节)+ 功能码 (0x03)+ 字节数( 1 个字节)+寄存器值(寄存器数量×2个字节)+校验码(2 个字节)
例如: 发送 01 03 00 00 00 02 C4 0B // 读取寄存器地址00和01的值
接收 01 03 04 00 00 00 01 3B F3 //返回寄存器地址00的值0,寄存器地址01的值01 。
06 功能码 功能:写单个寄存器
请求
地址(1 个字节)+功能码(0x06)+寄存器地址(2 个字节)+寄存器值2 个字节+校验码(2 个字节)
应答
地址(1 个字节)+ 功能码 (0x03)+寄存器地址(2 个字节)+寄存器值(2 个字节)+校验码(2 个字节)
例如: 发送 01 06 00 01 00 01 19 CA //写寄存器01值 01
接收 01 06 00 01 00 01 19 CA
16 功能码 功能:写多个寄存器
请求
地址(1 个字节)+功能码(0x10)+起始地址(2 个字节)+寄存器数量(2 个字节)
+字节数(1 个字节)+寄存器值(寄存器数量×2 个字节)+校验码(2 个字节)
应答
地址(1 个字节)+功能码(0x10)+起始地址(2 个字节)+寄存器数量(2 个字节)
+校验码(2 个字节)
例如 发送
01 10 00 00 00 02 04 00 00 00 01 32 6F //寄存器00写入00,寄存器01写入01
接收 01 10 00 00 00 02 41 C8
有了以上内容就可以制作上位机软件,由于前面用的一台电脑出故障,使用另台电脑时,发现原来的使用者安装好了VisualBasic6.0,在硬件条件具备的情况下,尝试做了个针对性的测试软件。
里面的设置为大家所熟悉的端口号、波特率、校验位等;因此串口的设置不复杂,与串口调试助手设置差不多。
初始化如下
MSComm.CommPort=Port ' 设定端口
MSComm.Settings = BaudRate & "," & ParityBit & "," & DataBit & "," & StopBit ' 设置波特率,无校验,8位数据位,1位停止位
MSComm.InBufferSize=1024 ' 设置接收缓冲区为1024字节
MSComm.OutBufferSize=4096 ' 设置发送缓冲区为4096字节
MSComm.InBufferCount=0 ' 清空输入缓冲区
MSComm.OutBufferCount=0 ' 清空输出缓冲区
MSComm.SThreshold=1 ' 发送缓冲区空触发发送事件
MSComm.RThreshold=1 ' 每X个字符到接收缓冲区引起触发接收事件
MSComm.OutBufferCount=0 ' 清空发送缓冲区
MSComm.InBufferCount=0 ' 清空接收缓冲
MSComm.PortOpen=True ' 打开串口
数据的接收
ReceiveArr=MSComm.Input ' 数据放入数组
数据的发送
MSComm.Output=SendArr ' 发送数据
有了以上的串口内容,笔者就开始尝试编写一个串口调试助手,然后根据接收的数据,经过处理就可以完成一个MODBUS通信测试软件了。但是很快发现制做一个串口调试助手需要花费时间还是比较多的,而且是一个大家常用的软件,那么是否有源代码呢?通过别人的原代码修改可以事半功倍。果然,网上有完整的代码,还有其它语言的代码。这样就找了个功能相对完整的串口RS485调试助手如图:
经过测试发现这个软件不完善:1、需要先发送数据之后,才能正常接收数据;2、长的数据被分成几个接收。对于第一问题不影响使用,如前所述MODBUS 是一个请求/应答协议,一发一收,上位机需先发数据。对于第二个问题,修改中断方式,利用定时器中断,然后控制好接收时间就可以了。
接下来就是数据的处理,MODBUS后面两字节采用的是CRC校验,计算效验码是个难点。代码如下:
Dim CRC1 As String
Public Sub CRC(STR1 As String)
Dim CRCREG As Long
Dim MVAL As Long
Dim R As Integer
Dim t As Long
Dim a As Long
Dim B As Long
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
a = CRCREG And 255
B = CRCREG And 65280
a = a * 256
B = B / 256
If (a + B) < 16 Then
CRC1 = "000" + Hex(a + B)
ElseIf (a + B) < 256 Then
CRC1 = "00" + Hex(a + B)
ElseIf (a + B) < 4096 Then
CRC1 = "0" + Hex(a + B)
Else
CRC1 = Hex(a + B)
End If
End Sub
接下来就是对号入座了。原本打算给每个寄存器放一个读按键和写按键,后面发现单个读取写入不实用,改成一次读取多个参数。由于有一百多个寄存器超过了上限,因此分两条指令,收到第一条应答后发送第二条,写参数也是同样的处理。
读参数的处理
Select Case i 'i代表寄存器
Case 0
Case 1
c1.Text = ReceiveArr(i * 2 + 4 - commandstart * 2)
写参数的处理:
Select Case i
Case 1 '代表寄存器
DATA_SENT(i * 2 + 7) = 0
If Int(ADDR.Text) < 100 Then
DATA_SENT(i * 2 + 7 + 1) = Int(ADDR.Text)
Else
err.Show’超过输入范围,跳出提示框
GoTo Err
End If