《基于Modbus协议的串口通讯程序》
东风汽车有限公司计量测试中心 王德宪
内容摘要:本文在遵循Modbus协议的基础上,阐述了Modbus的两种传输模式和串口通讯程序的设计实例,并给出了VB语言的程序清单。可供有关技术人员在相关设计和应用工作中参考。
关键词:Modbus协议、串口通讯、VB程序
1. Modbus协议简介
Modbus 协议是一种国际通用的通信标准。我国在2004年9月发布了GB/Z19582《基于Modbus协议的工业自动化网络规范》,2008年3月国家标准化管理委员会正式批准为GB/T 19582-2008。
Modbus协议规定每个控制器必须有自己的设备地址。当一个控制器在Modbus网络上接收到一条消息时,首先要识别是否发给自己。如果是并且需要回应,控制器将生成反馈消息并依据Modbus协议发出。
不论是何种控制器经过何种网络进行通信,Modbus协议定义了一个控制器能够识别的消息域的框架结构和内容的公共格式。它描述了一个控制器请求访问其它设备的过程,以及如何回应来自其它设备的请求。还规定了如何侦测和记录错误。
遵循Modbus协议,不同厂家生产的控制设备可以连接在同一个工业网络。在这个网络上,主机设备可以方便地对各种控制设备进行集中监控,各控制设备之间也可以直接相互通信。
1.1 Modbus的两种传输模式
Modbus的两种传输模式分别是ASCII(美国标准信息交换代码)和RTU(远程终端装置)。在同一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。
1.1.1 ASCII模式
使用ASCII字符帧时,位的发送序列是:
有奇偶校验:
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 奇偶位 | 停止位 |
无奇偶校验:
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 停止位 | 停止位 |
当控制器设为在Modbus网络上以ASCII模式通信时,在消息中的每个字节(8Bit)都作为一个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。
ASCII模式的消息帧结构:
冒号符 | 地址码 | 功能代码 | 数据数量 | 数据1 | ... | 数据n | LRC高字节 | LRC低字节 | 回车符 | 换行符 |
1.1.2 RTU模式
使用RTU字符帧时,位的发送序列是:
有奇偶校验:
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 奇偶位 | 停止位 |
无奇偶校验:
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 停止位 | 停止位 |
当控制器设为在Modbus网络上以RTU模式通信时,在消息中的每个字节(8Bit)包含两个4Bit的十六进制字符。这种方式的主要优点是:在同样的波特率下,可比ASCII方式传送更多的数据。
RTU模式的消息帧结构:
地址 | 功能代码 | 数据数量 | 数据1 | ... | 数据n | CRC低字节 | CRC高字节 |
1.2 Modbus的消息帧结构
在ASCII或RTU两种传输模式中,传输设备将Modbus消息转换为有起点和终点的帧,这就可以使接收设备在消息起始处同步接收数据,读取地址信息,判断哪一个设备被选中,判知何时信息已传送完毕。有些消息也能侦测到错误并且能返回结果。
1.2.1 ASCII帧
使用ASCII模式,每帧消息以冒号(:)字符(ASCII码 3AH)开始,以回车换行符结束(ASCII码 0DH,0AH)。
其它域可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不断侦测“:”字符,当有一个冒号接收到时,每个从设备都解码下个域(地址域)来判断是否是发给自己的。
消息中字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为传输错误。一个典型消息帧如下所示:
起始位 | 地址域 | 功能码域 | 数据域 | LRC校验域 | 结束符 |
1个字符 | 2个字符 | 2个字符 | n个字符 | 2个字符 | 2个字符 |
1.2.2 RTU帧
使用RTU模式,发送每帧消息之前至少要有3.5个字节时间的停顿。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不断侦测网络总线,当接收到地址域时,每个从设备都进行解码以判断是否发给自己。在最后一个传输字节之后,应至少停顿3.5个字节时间,一个新的消息必须在此停顿后再开始。
起始位 | 地址域 | 功能码域 | 数据域 | CRC校验域 | 结束符 |
停顿3.5个字节 | 8Bit | 8Bit | n个8Bit | 16Bit | 停顿3.5个字节 |
1.2.3 消息帧中各域的说明
地址域包含两个字符(ASCII)或8Bit(RTU)。允许的从设备地址范围是十进制的0~247。主设备将要联络的从设备的地址放入消息中的地址域来选通从设备。当从设备发送回应消息时,它把自己的地址放入回应的地址域中,以便主设备知道是哪一个设备作出的回应。地址0用作广播地址,以使所有的从设备都能认识。当Modbus协议用于更高水准的网络时,广播可能被禁止或以其它方式代替。
功能码域包含了两个字符(ASCII)或8Bit(RTU)。可能的代码范围是十六进制的01H~FFH。
数据域可以由若干个ASCII字符或RTU字节组成。数据域的值由十六进制数构成,范围为00H~FFH。数据域包含了寄存器地址、要处理的数据字节数以及相应的数据值。
错误检测域包含两个字符(ASCII)或一个16Bit值(RTU)。错误检测域的内容是通过LRC(纵向冗长检测)或CRC(循环冗长检测)方法得出的。需要注意的是:LRC的高位字节在低位字节之前,而CRC的低位字节在高位字节之前。
2. 串口通讯程序设计实例
为了便于理解,下面列举一种采用RTU模式通讯的应用实例。这个实例的硬件由一台计算机和分布在10个房间的10块温湿度表组成RS485网络架构。温湿度表的地址分别设定为01H至0AH。计算机读各温湿度表数据的命令消息帧包含8个字节:
地址域 | 功能码域 | 数据域 | 错误校验域 | ||||
单字节 | 单字节 | 读寄存器地址高字节 | 读寄存器地址低字节 | 读寄存器个数高字节 | 读寄存器个数低字节 | CRC低字节 | CRC高字节 |
0XH | 04H | 00H | 00H | 00H | 02H | XXH | XXH |
被点名的温湿度表接收到上述命令消息后,向计算机发送温湿度数据,该消息帧包含11个字节:
地址域 | 功能码域 | 数据域 | 错误校验域 | |||
单字节 | 2字节的命令数据 | 2字节的温度数据 | 2字节的湿度数据 | 2字节的露点数据 | CRC低字节 | CRC高字节 |
0XH | XXXXH | XXXXH | XXXXH | XXH | XXH | XXH |
3.VB语言设计的上述实例的串口通讯程序清单
Private Declare Function timeGetTime Lib"winmm.dll" () As Long
Public btLoCRC As Byte, btHiCRC As Byte, t0 AsLong, t1 As Long, t2 As Long, t3 As Long
Public Rnumber As Integer, ii As Integer, i AsInteger, j As Integer, k As Integer, ReadT, Crc
Dim TemperatureData(10),HumidityData(10)
Private Sub Form_Load()
Me.Height = 6660
Rnumber = 10'房间数量(每个房间装1块温湿度表)
ReadT = 10'每10秒读一轮温湿度表数据
IfMSComm1.PortOpen = True Then MSComm1.PortOpen = False'如果串口1是打开状态则关闭它
With MSComm1'设置串口参数
.CommPort = 1'指定使用串口1
.Settings = "9600,N,8,1"'波特率9600bit/s,无校验,8个数据位,1个停止位
.InputMode = comInputModeBinary'发送二进制数值(=comInputModeText为发送字符)
.InputLen = 50'从接收缓冲区中可一次性读取的数据个数
.InBufferCount = 0'清空接收缓冲区
.OutBufferCount = 0'清空发送缓冲区
.RThreshold = 5 + 2 * 2'设置成接收9个字节就产生OnComm事件
.InBufferSize = 512'设置接收缓存区容量
.OutBufferSize = 512'设置发送缓存区容量
MSComm1.PortOpen = True'打开串口1
EndWith
Timer1.Interval = 100'定时器1定时100毫秒
Timer1.Enabled = True'定时器1开始计时
End Sub
Private Sub Timer1_Timer()'定时发送(读数据的)命令
Timer1.Enabled = False'定时器1停止计时
t0= timeGetTime'从系统取得当前(开始读温湿度表) 时刻
Dimtbisend(7) As Byte'定义发送数据的数组
IfMSComm1.PortOpen = True Then
For k = 1 To Rnumber'依次向各个房间的温湿度表发送读命令
ii = k
tbisend(0) = "&h" + Hex(k)'被呼叫子机的地址码
tbisend(1) = "&h" + Hex(4)'4是读寄存器的功能码
tbisend(2) = "&h" + Hex(0)'被读寄存器的起始地址高字节
tbisend(3) = "&h" + Hex(0)'被读寄存器的起始地址低字节
tbisend(4) = "&h" + Hex(0)'一次读寄存器的个数的高字节
tbisend(5) = "&h" + Hex(2)'一次读寄存器的个数的低字节
Crc = CRC16(tbisend, 6, btLoCRC, btHiCRC)'计算tbisend(0)~tbisend(5)的CRC校验值
tbisend(6) = "&h" + Hex(btLoCRC)'CRC低位
tbisend(7)= "&h" + Hex(btHiCRC)'CRC高位
If MSComm1.PortOpen = False Then MSComm1.PortOpen = True
MSComm1.Output = tbisend'发送数据
t1 = timeGetTime
While timeGetTime < t1 + 100 '延时等待100毫秒,以便有足够时间接收从机发来的数据
DoEvents
Wend
Text1(k - 1).Value = TemperatureData(k)'显示温度值
Text2(k - 1).Value = HumidityData(k)'显示湿度值
Next k
EndIf
t2= timeGetTime'从系统取得当前(结束读温湿度表) 时刻
t3= t2 - t0'算出读温湿度表的耗时
Timer1.Interval = ReadT * 1000 - t3'定时器1定时,如果不减去T3,会使读周期变长
Timer1.Enabled = True'定时器1开始计时
End Sub
Private Sub MSComm1_OnComm()'接收数据
Dim TemperatureData6 As String, HumidityData6 AsString
Dim INByte() As Byte
IfMSComm1.CommEvent = comEvReceive Then'如有接收事件发生,则响应并作计算
INByte = MSComm1.Input'接收数据
If INByte(0) = ii And INByte(1) = 4 Then'如收到的地址码=被叫从机地址并且功能码=读寄存器,
'则将收到的CRC码与收到的数据计算出的CRC码比较
Crc = CRC16(INByte, UBound(INByte) - LBound(INByte) - 1, btLoCRC,btHiCRC)'计算收到数据的CRC校验值
If INByte(UBound(INByte) - 1) = btLoCRC And INByte(UBound(INByte))= btHiCRC Then '如校验正确则计算
TemperatureData6 = Hex(INByte(3)) &Format(Hex(INByte(4)), "00")'将温度转换成十六进制
HumidityData6 = Hex(INByte(5)) &Format(Hex(INByte(6)), "00")'将湿度转换成十六进制
TemperatureData(ii) = Format(Val("&H"& TemperatureData6) / 10, "##0.0")'将温度转换为十进制
HumidityData(ii) = Format(Val("&H"& HumidityData6) / 10, "##0.0")'将湿度转换为十进制
End If
End If
MSComm1.InBufferCount = 0'清接收缓存
EndIf
End Sub
Function CRC16(Data() As Byte, No As Integer,CRC16Lo As Byte, CRC16Hi As Byte) As String
DimCL As Byte, CH As Byte, SaveLo As Byte, SaveHi As Byte
CRC16Hi = &HFF'为16位CRC校验寄存器赋初始值 FFFFH
CRC16Lo = &HFF
CH= &HA0'为16位CRC校验多项式赋初始值 A001H
CL= &H1
Fori = 1 To No
CRC16Lo = CRC16Lo Xor Data(i - 1)'将被校验的每个字节数据依次与CRC校验寄存器进行异或
For j = 1 To 8'8次移位
SaveHi = CRC16Hi
SaveLo = CRC16Lo
CRC16Hi = CRC16Hi 2'高位右移一位
CRC16Lo = CRC16Lo 2'低位右移一位
If ((SaveHi And &H1) = &H1)Then'如果高位字节最右一位为1,则低位字节最左位补1,否则补0
CRC16Lo = CRC16Lo Or &H80
End If
If ((SaveLo And &H1) = &H1)Then'如低位字节最右一位为1,则与多项式值异或
CRC16Hi = CRC16Hi Xor CH
CRC16Lo = CRC16Lo Xor CL
End If
Next j
Next i
End Function
参考文献:
①《基于Modbus协议的工业自动化网络规范》GB/T 19582.1-2008
②《Modbus协议中文版》可在互联网上下载