DSP

Modbus协议栈模型

2019-07-13 16:47发布



1:Modbus协议简介

    Modbus协议主要描述的是应用层的信息封装格式,处于OSI模式的第七层(应用层)。Modbus的物理层可以是RS-485、Ethernet II /802.3。Modbus协议栈的层次图:             
        本文主要介绍Modbus使用物理层是EIA/TIA-485的情况。Modbus主要内容为应用层协议,所以在现实使用中可以将EIA/TIA-485(485)和Ethernet II /802.3(以太网)不同的物理连接网络通过Modbus协议组成统一的系统。 使用EIA/TIA-485这种串行通讯方式的Modbus协议框图:

物理层的EIA/TIA-485大家比较熟悉,现场总线通讯很多设备都使用485通讯。下面重点介绍一下Figure 2 中的 Data Link 层和Application层。 Application层主要目的是定义应用层的功能和参数。应用层帧格式见图:
Modbus应用层的Function Code 就是我们熟悉的功能码,常见的有04(read input register) 03(Read Holding Registers)等。这些内容大家比较熟悉,就不一一说明了。 具体功能码含义可参见文章最后的相关资料及工具。
Data Link 层主要目的是确定总线上的具体从机以及检验数据帧传输是否正确。Data Link层的数据帧格式见图:

其中Address field域是地址域,现场使用485总线,总线上可能存在很多设备,这个地址主要用于从总线上选择一个具体的设备。CRC (or LRC是检验位)用于检验数据,确保传输正确。 在485总线上使用Modbus协议,有两种主要方式:RTU ASCII。RTU传输方式传输的数据帧为二进制数据,ASCII传输方式传输的是ASCII码。举例来说要发送 0x31(十进制数)使用RTU直接发送0x31就可以(占用一个byte),而ASCII码传输方式则需要发送0x33和0x31这两个字节(即十六进制数0x31的 高低位分别占用一个byte)。 两种传输方式各有利弊,使用RTU传输效率高,使用ACSII可读性好。 Modbus协议是一种主从式协议,即主站发起通讯,从站响应这种模式。  最后,给大家一个看一个完整的Modbus协议帧格式:
关于Modbus的具体协议规定和Modbus在串行传输(485)中的使用,请大家查看4:相关资料及工具 里的
Modbus_Application_Protocol_V1_1b3.pdf
Modbus_over_serial_line_V1_02.pdf

其他Modbus相关资料请参考网站:http://www.modbus.org/

2:FreeMODBUS移植

    FreeMODBUS是一个开源的MODBUS协议栈,使用ANSI C语言编写,可在常见的处理器上移植(不需要嵌入式操作系统的支持,当然可以通过很简单的修改可以在嵌入式操作系统上运行)。FreeMODBUS支持从机,若想使用FreeMODBUS实现主机MODBUS,则需要根据实际需求进行二次开发MODBUS协议栈。 关于Modbus的软件结构,移植,配置,使用网上有很多资料和移植经验。我就不详细探讨移植和FreeMODBUS的软件结构。这里只是简单说一下软件的大致结构以及移植过程中需要注意的地方。我会根据MODBUS协议栈的协议规定,以FreeMODBUS为原型,说明从协议栈的规定到具体实现之间的关系。另外,也谈一下自己对通讯协议的一些个人想法。 前面说过,MODUBS串行口传输方式中存在RTU、ASCII 两种方式。这里只针对RTU方式进行分析,ASCII方式自行分析。  FreeMODBUS的整体软件结构是:前后台系统。利用串行口接收、发送中断完成数据帧在物理层的传输:接收时,接到一帧完整无误的数据帧就通过事件的方式通知前台程序,前台程序根据收到的数据帧做相应的处理。发送时,先封装应用层数据内容,然后再封装链路层数据,最后通过串口中断的方式发送出去。
通过分析FreeMODBUS的整体软件结构我们很容易就知道,FreeMODBUS需要使用到串口(RS485),我们移植FreeMODBUS要实现初始化串口、读写串口、初始化并控制串口中断。 接下来一个重要的问题是RTU方式的MODBUS如何判别一帧数据包哪?有的协议里有开始标示、结束标示,通过判别开头标示和结束标示来表示一帧完整的数据帧。但根据前面的介绍,我们发现MODBUS RTU方式的数据帧并没有开始标示和结束标示,那MODBUS RTU怎么判别一帧数据帧的哪?我们还要看MDBUS协议栈的具体规定。先给大家看一个图:
MODBUS协议里面说一帧数据和下一帧数据之间的间隔至少是3.5个字符。按照9600的波特率来算的话(9600 N 1,一帧数据为10bit),1S大约能传输960字节,一个字符(1 Byte)的传输时间大约为1ms多点。那这个T3.5应该在4ms左右。也就是说一帧完整的数据帧和下一个数据帧之间的间隔应>=4ms。也就是图示中的 Start >= 3.5Char 、End >= 3.5 Char。 MODBUS RTU方式 还有一个时间要求:
根据图示,很明显MIDBUS协议要求一帧数据里,byte和byte之间的时间间隔应<= 1.5个字符,如果还是按照9600的波特率来计算的话,大约是1.56ms。一帧数据内超出这个数值认为数据包错误。
我们知道了这个,似乎明白了MODBUS协议栈是使用两个时间来确定一帧完整的数据包的。记住哦,T3.5 T1.5 很重要的MODBUS协议参数。然后我们也很容易想到FreeMODBUS要实现精确延时,有可能会使用到定时器,对了,你没猜错,FreeMODBUS除了占用一个串口硬件资源外,它还占用了一个定时器硬件资源。所以移植的时候也要处理定时器有关的初始化、启动定时、关闭定时、开关定时器中断等操作。 下面,我们来说一下FreeMODBUS是如何实现上述时间要求的。根据我看FreeMODBUS的源代码,它只利用了一个T3.5,并没有实现T1.5这个时间要求。我的理解是这样的: FreeMODBUS一般作为从机,从机作为接收时,若主发送的数据不满足T1.5的要求,FreeMODBUS并没有检测,FreeMODBUS只检测是否满足T3.5这个时间,若byte和byte之间的间隔>=T3.5 这个时间,则认为一帧数据包接收完毕。它的设计可能是假定了主机发送的数据帧格式完全符合MODBUS协议。
而从机发送数据时,是使用串口中断发送,肯定满足T1.5的要求,因为MODBUS是主从模式,主机发送完命令,从机接收到后才给主机响应,这种通讯业肯定能满足T3.5这个要求。 好了,我们现在看看FreeMODBUS是怎么实现接收数据帧时根据这个T3.5来实现判断接收完一帧完整的数据帧的。大致流程是这样,初始化一个4ms的定时器,并打开定时器中断,在每次接收到1个byte时,都重置定时器计数值并启动定时器,然后若时间都过去4ms了都没有收到新的byte,那么就认为一帧数据接收完毕。这个也很好理解吧,可以好好琢磨一下这个实现方案。
最后,还有一个事件处理需要在移植FreeMODBUS的时候考虑,这个简单,其实就是在接受完一帧数据或者出错后,通知前台的查询程序,让它做相应的处理。
FreeMODBUS大致分了3层,底层的接收、发送策略(符合MODBUS协议规定)。中间MODBUS协议。然后就是上层应用层,每层的接口设计的不错,方便移植,应用层也方便扩展。


 FreeMODBUS 的官方网站:www.freemodbus.org

3:PLC modbus 地址

            MODBUS协议及FreeMODBUS的基本工作原理、移植都介绍完了。我们接下来看看MODBUS实际应用时的地址一般是怎么定义的。 先说一下标准MODBUS协议里的地址和设备定义的地址之间的关系。见下图:
也就是说,一般设备定义的应用层地址是MODBUS协议里地址数值+1。这么说可能不太容易理解,我举个例子吧,我们做了一个设备支持MODBUS协议,我们给用户的使用说明书里说 0x0001这个地址是一个模拟输入量(比如说是压力),根据上面的MODBUS要求,我们发送的MODBUS协议里的地址域里应该填写的内容是0x0001 - 0x0001 = 0x0000。即封装成标准MODBUS协议帧时的地址是0。其实,这里的Application specific 是指具体应用时的地址。这个地址只和具体使用一个设备时有关,这个地址和MODBUS协议里的地址域里的地址关系见上图。
MODBUS使用最多的是PLC,我们就说说这个MODBUS通讯协议里规定的地址和PLC定义的应用层地址之间是个什么关系?
通常PLC 定义的  Modbus 地址由 5 位数字组成,包括起始的数据类型代号,以及后面的偏移地址。Modbus Master 协议库把标准的 Modbus 地址映射为所谓 Modbus 功能号,读写从站的数据。Modbus Master 协议库支持如下地址: 
00001 - 09999:数字量输出( 线圈)          
10001 - 19999:数字量输入(触点) 
30001 - 39999:输入数据寄存器(通常为模拟量输入)
40001 - 49999:数据保持寄存器
也就是说,PLC编程时只需要设置读取、写入,还有5位PLC MODBUS地址即可,Modbus Master 协议库会根据你填写的地址内容自动转换为相应的标准MODBUS协议包内容,主要是根据5位地址中的第一位和设置的读取还是写入来生成MODBUS协议里的功能码和地址。
这里不好理解,我还是举例。比如有这样一个系统需求:PLC采集压力数据,根据压力数据用来控制电机。PLC作为主机,压力采集器作为从机,他们之间通过MODBUS协议通讯。压力采集器支持标准的modbus协议。 我们想通过PLC读取一路压力数据。我们在PLC端编程时,设置成   读取  地址写成 30001(十进制数)。其实通过Modbus Master 协议库会把这样的一个请求命令转换为标准的MODBUS协议。 具体为:根据30001这个数据我们知道这个是一个输入数据寄存器(通常为模拟量输入),在modbus协议栈里这个对应的是 Input Register ,而我们是读取。所以这个功能是Read Input Register (对应的标准MODBUS协议的功能码是0x04) ,而地址我们先去掉最高位的3,剩下0001(十进制),然后根据MODBUS协议规定,我们应该再减去1,生成标准MODBUS协议。即地址为0。转换完后是这样的一个结果,功能码是0x04(1 byte),地址是0x0000(2 byte)

4:相关资料及工具

                 Modbus_Application_Protocol_V1_1b3.pdf
   Modbus_over_serial_line_V1_02.pdf         freeModbus代码解读及移植笔记.doc         ModBus协议--易理解.doc         ModbusPOLL工具