Modbus是一种工业协议,于1979年开发,旨在实现自动化设备之间的通信。Modbus最初是作为通过串行层传输数据的应用级协议实现的,现已扩展到包括通过串行、TCP/IP和用户数据报协议(UDP)的实现。Modbus已经成为工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式,在实际使用中如果需要与PLC或者其他智能设备连接并进行通讯,对Modbus协议的掌握是必不可少的。本节将介绍如何在.NET平台下实现Modbus协议,并通过串行和TCP完成通信过程。
1. Modbus协议
Modbus是使用主从关系实现的请求-响应协议,如图1所示。在主从关系中,通信总是成对发生。一个设备必须发起请求,然后等待响应,并且发起设备(主设备)负责发起每次交互。通常主设备是人机界面(HMI)或监控和数据采集(SCADA)系统,从设备是传感器、可编程逻辑控制器(PLC)或可编程自动化控制器(PAC)。
图1 Modbus的主从关系示意图
如果按照国际ISO/OSI的7层网络模型来说,标准Modbus协议定义了通信物理层、链路层及应用层,如图2所示,每层的定义如下:
物理层:定义了基于RS-232和RS-485的异步串行通信规范
链路层:规定了基于站号识别、主/从方式的介质访问控制
应用层:规定了信息规范(或报文格式)及通信服务功能
图2 7层网络模型中的Modbus协议
在最初的做法中,Modbus是建立在串行端口之上的单一协议,因此它不能被分成多个层。随着时间的推移,该协议引入了不同的应用程序数据单元来更改串行通信使用的数据包格式,或允许使用TCP/IP和用户数据报协议(UDP)网络。这实现了定义协议数据单元(PDU)的核心协议和定义应用数据单元(ADU)的网络层的分离。
目前很多Modbus设备的物理层都是基于RS-232/RS-485,也有只使用Modbus的应用层(信息规范),而底层使用其他通信协议,如使用TCP/IP和UDP的Modbus网络,或者底层使用无线扩频通信Modbus网络等。
2. 协议数据单元(PDU)
PDU及其处理代码构成了Modbus应用协议规范的核心。该规范定义了PDU的格式、协议使用的各种数据概念、如何使用功能代码访问数据,以及每个功能代码的具体实现和限制。Modbus PDU格式被定义为一个功能代码,后面跟着一组关联的数据。该数据的大小和内容由功能代码定义,整个PDU功能代码和数据的大小不能超过253个字节。每个功能代码都有一个特定的行为,从设备可以根据所需的应用程序行为灵活地实现这些行为。PDU规范定义了数据访问和操作的核心概念,但是从设备可能会以规范中未明确定义的方式处理数据。
通常,Modbus可访问的数据存储在以下四个存储区或地址范围的其中一个:线圈、输入线圈、输入寄存器和保持寄存器。这些数据库定义了所包含数据的类型和访问权限。从设备可以直接访问这些数据,因为这些数据由设备本地托管。Modbus可访问的数据通常是设备储存的一个子集。
Modbus协议将每个存储区定义为包含多达65536个元素的地址空间。在PDU的定义中,每个数据元素的地址范围从0到65535。但是,每个数据元素的编号从1到n,其中n的最大值为65536。也就是说,线圈状态1位于地址0的线圈状态区块中,而保持寄存器54在从机被定义为保持寄存器的内存部分中的地址是53。
虽然规范将不同的数据类型定义为存在于不同的存储区中,并为每种类型分配一个本地地址范围,但这并不一定会转化为用于记录或理解给定设备的Modbus可访问内存的直观编址方案。为了简化对存储区位置的理解,引入了一种编号方案,其将前缀添加到所讨论的数据的地址中。例如,设备手册不会引用地址13寄存器14的数据项,而是引用地址4014、40014或400014的数据项。在任何情况下,第一个数字都是4,表示保持寄存器,剩余数字则表示指定地址。4XXX、4XXXX和4XXXXX的区别取决于设备使用的地址空间。如果所有65536个寄存器都在使用中,应该使用4XXXXX符号,因为其允许范围为400001~465536。如果只使用几个寄存器,通常的做法是使用范围4001到4999。所有存储区的标识以及地址范围如表1所示,其中最大地址范围XXXX都是与设备相关。
表1 存储区标识和地址范围
这里需要指出的是线圈表达其实是布尔量数值,操作类似数字I/O的方式,其中地址0XXXX地址是既可以读取又可以写入,只要控制的是DO的状态,可以控制线圈的开合。但是地址1XXXX的则是只读的,主要是来读取线圈的状态的,主要读取的是DI的状态。地址3XXXX是输入寄存器,这也是只读的,可以读取设备中的数值,比如说设备读到温度数值,电压数值等等,类似于读取AI操作。地址4XXXX是保持/输出寄存器,也是在设备中最常用的寄存器,既可以读取一些模拟数值,可以写入模拟数值做一些操作,类似于AO输出操作,比如阀门打开的程度。
与数据模型可能因设备而异不同,功能代码及其数据由标准明确定义。每个功能都遵循一种模式。首先,从设备会验证功能代码、数据地址和数据范围等输入。然后执行所请求的操作并发送与代码相符的响应。如果此过程中的任何步骤失败,则会向请求程序返回异常。这些请求的数据传输就称为PDU。PDU由一个单字节的功能代码组成,后面跟着多达252字节的针对特定函数的数据,如图3所示。
图3 PDU格式
功能代码是第一个需要验证的项。如果功能代码没有被接收到请求的设备识别,则会回应一个异常。如果功能代码被接受,则从设备根据功能定义开始分解数据。由于数据包大小限制为253字节,设备可传输的数据量有限。最常见的功能代码是240到250字节的从设备数据模型数据,具体取决于代码。每个标准功能代码的定义都包含在设备的说明书中。即使对于最常见的功能代码,在主设备上启用的功能与从设备可以处理的功能之间也存在不可避免的不匹配。建议任何文档都遵循测试规范,并根据其支持的代码而不是传统分类来定义它们的一致性。表2列举了四种存储区中常用的功能代码。
表2 存储区及其对应的功能代码
最后我们来了解下异常。从设备使用异常来指示各种不良状况,比如错误请求或不正确输入。但是,异常也可以作为对无效请求的应用程序级响应。从设备不响应发出异常的请求。相反,从设备忽略不完整或损坏的请求,并开始等待新的消息传入。异常以定义好的数据包格式报告给用户。首先将一个功能代码返回给等同于与原始功能代码的请求主设备,除了设置了最高有效位。这等同于为原始功能代码的值加上0x80。异常响应包括一个异常代码来代替与给定函数响应相关的正常数据。在标准内,四种最常见的异常代码是01、02、03和04。表3介绍了常见的Modbus异常代码以及标准含义。
表3 常用Modbus异常代码和含义
3. 应用数据单元(ADU)
除了Modbus协议的PDU核心定义的功能外,Modbus通信还可以使用多种网络协议。最常见的协议是串行和TCP/IP,但也可以使用其他协议比如UDP。为了在这些层之间传输Modbus所需的数据,Modbus包含一组适用于每种网络协议的ADU。
Modbus需要某些功能来提供可靠的通信。ADU格式中会包含单元ID或地址,为应用层提供路由信息。每个ADU都带有一个完整的PDU,其中包含给定请求的功能代码和相关数据。为了可靠性,每条消息都包含错误检查信息。最后,所有的ADU都提供了一种机制来确定请求帧的开始和结束,但实现这些机制的方式各不相同。
ADU的三种标准格式是TCP、远程终端单元(RTU)和ASCII。RTU和ASCII ADU通常用于串行线路并且之间不能混用,而TCP则用于现代TCP/IP或UDP/IP网络。
图4 TCP ADU的报文格式
TCP ADU由Modbus应用协议(MBAP)报文头和Modbus PDU组成。MBAP是一个通用的报文头,依赖于可靠的网络层。此ADU的格式(包括报文头)如图4所示,每部分的介绍如下:
报文头的数据字段代表其用途。首先它包含一个事务处理标识符,这有助于网络允许同时发生多个未处理的请求。也就是说,主设备可以发送请求1、2和3。在稍后的时间点,从设备可以以2、1、3的顺序进行响应,并且主设备可以将请求匹配到响应并准确解析数据,这对以太网网络很有用。
协议标识符通常为零,但可以用它来扩展协议的行为。协议使用长度字段来描述数据包其余部分的长度。这个元素的位置也表明了这个报文头格式在可靠的网络层上的依赖关系。由于TCP数据包具有内置的错误检查功能,可确保数据一致性和传送,因此数据包长度可位于报文头的任何位置。在可靠性较差的网络上(比如串行网络),数据包可能会丢失,其影响是即使应用程序读取的数据流包含有效的事务处理和协议信息,长度信息的损坏也会使报文头无效。TCP为这种情况提供了适当的保护。
单元ID 通常不在TCP/IP设备中使用。但是,Modbus是一种常见的协议,因此通常会开发一些网关来将Modbus协议转换为另一种协议。在最初的预期应用中,Modbus TCP/IP转串行网关用于连接新的TCP/IP网络和旧的串行网络,这时单元ID用于确定PDU对应的从设备的地址。
最后,ADU包含一个PDU。对于标准协议,PDU的长度仍限制为253字节。
RTU ADU的报文格式看起来要简单得多,如图5所示。与较为复杂的TCP/IP ADU不同的是,除了核心PDU之外,该ADU仅包含两条信息。首先,地址用于定义PDU对应的从设备。在大多数网络中,地址0定义的是“广播”地址。也就是说,主设备可以发送输出命令到地址0,而所有从设备应处理该请求,但是不做出任何响应。除了这个地址外,CRC还用于确保数据的完整性。最后,数据包的首尾包含一对3.5个字符表示的沉默时间(silent time),即总线上没有通信的时段,或者可以认为是报文间隔时间。对于9600的波特率,这个速率大约是4ms。该标准定义了一个最小沉默长度,不论波特率如何,都低于2ms。
图5 RTU ADU的报文格式
ASCII ADU的报文格式比RTU更复杂,它为每个数据包定义了一个明确且唯一的开始和结束,如图6所示。每个数据包以“:”开始并以回车(CR)和换行符(LF)结束。因此通过第1章节中介绍的串口类库可以轻松读取缓冲区中的数据,直到收到特定字符CR/LF为止。这些特性有助于在现代应用程序代码中有效地处理串行线路上的数据流。
图6 ASCII ADU的报文格式
ASCII ADU的缺点是所有数据都以ASCII编码的十六进制字符进行传输。也就是说,针对功能代码3(0x03)发送的不是单个字节,而是发送ASCII字符“0”和“3”。这使协议更具可读性,但也意味着必须通过串行网络传输两倍的数据,并且发送和接收应用程序必须能够解析ASCII值。
除了串行和TCP之外,Modbus还可以在许多网络层上运行。一个可能的实现是UDP,因为它适合于Modbus通信风格。Modbus本质上是基于消息的协议,因此UDP能够发送明确定义的信息包,而不需要任何额外的应用程序级信息,如起始字符或长度,这使得Modbus非常易于实现。Modbus PDU数据包可以使用标准的UDP API发送,不需要额外的ADU或重新使用现有的ADU,并由另一端完全接收。建议的做法是在UDP网络层上使用TCP/IP ADU。
4. 开源类库NModbus4
Modbus协议的应用层实际上是对底层实际发送数据格式进行了编码,可以看到的是底层实际发送数据格式还是字节类型,只要了解每个字节代表含义就可以完成解码,得到想要的数据。本小节中我们将会介绍使用开源类库NModbus4来轻松完成Modbus应用层的工作。NModBus4项目的GitHub地址为https://github.com/NModbus4/NModbus4,支持TCP、UDP、RTU等Modbus协议,可用于对Modbus从设备的连接和通信。
先简单介绍一下NModbus4类库中的几个重要方法,如表4所示。
表4 NModbus4类的方法
利用这些方法就完成对Modbus所有的应用层操作,实现完整的Modbus协议层编写,而不需要管物理层的事情,包括读取/写入线圈,读取/写入寄存器等等。接下来我们来看两个具体的实例,一个基于串口,另外一个基于TCP。
基于串口的Modbus实现
在《C#与开源虚拟仪器技术》书的实例13.3.4_1中我们编写了一个基于串口的Modbus程序,基本上集成了Modbus常用的功能,界面如图7所示。
图7 串口Modbus程序界面
为了验证Modbus程序的功能,可以安装Modbus Slave这款测试Modbus协议的软件,它可以仿真从站设备。图8中我们配置了Modbus串口信息,包括波特率校验位等。
图8 在Modbus Slave中配置Modbus串口信息
图9对从站功能进行配置,配置位开启保持寄存器(4XXXX),寄存器可读可写。
图9 在Modbus Slave中配置从站功能
打开实例程序,配置好串口选项,和Modbus Slave保持一致,然后打开串口。在功能码中选择“10H-写入多个保持寄存器”,从站号1,起始地址0,输入需要写入数据用空格隔开。点击执行按钮Read/Write后,可以在Modbus Slave看到这几个地址的寄存器已经被修改了,如图10所示。
图10 写入数据到多个寄存器
在功能码中选择“03H-读取多个保持寄存器”,从站号1,起始地址0,读取长度为5。点击执行按钮Read/Write后,可以在demo程序中看到已经将这几个数据读取出来了,如图11所示。
图11 从多个保持寄存器中读取数据
基于TCP的Modbus实现
在《C#与开源虚拟仪器技术》书中的实例13.3.4_2是基于TCP的Modbus协议实现,同样也集成了Modbus常用的功能。界面如图12所示。
图12 TCP Modbus程序界面
由于每个从站设备都相当于服务器,所以要首先配置好从站设备,在连接建立上选择TCP/IP模式,并且设置空闲端口号,如图13所示。
图13 在Modbus Slave中配置Modbus TCP/IP信息
然后配置从站功能,当前配置位开启保持寄存器(4XXXX),寄存器可读可写功能。打开实例程序,IP地址选择127.0.0.1,这地址本机的IP回环地址,同时选择端口号为502,点击开始按钮。在功能码中选择“10H-写入多个保持寄存器”,从站号1,起始地址0,输入需要写入数据用空格隔开。点击执行按钮Read/Write后,可以在Modbus Slave看到这几个地址的寄存器已经被修改了,如图14所示。
图14 写入数据到多个寄存器
在功能码中选择“03H-读取多个保持寄存器”,从站号1,起始地址0,读取长度为5。点击执行按钮Read/Write后,可以在demo程序中看到已经将这几个数据读取出来了,如图15所示。
图15 从多个保持寄存器中读取数据
之前只介绍了利用NModbus4类库做为Master的方法,实际利用这个类库也可以作为Slave,这样可以将运行程序的设备(工控机)也作为节点一部分,方便与其他系统进行网络(低速的)互联,特别是可以方便其他设备对应用程序进行控制和观察特征数值等等操作。
范例如图16所示,分为Slave端和Master端,首先填入设备的监听IP,这边填写的是127.0.0.1就是监听本机,如果可以监听任意IP建议修改成为listener = new TcpListener(IPAddress.Any, 502);
这里唯一需要理解的是:
slave.DataStore = DataStoreFactory.CreateDefaultDataStore(); //创建寄存器存储对象
这就是创建,Modbus协议中的65536个元素的地址空间。查看DataStore的定义,分别定义了四种寄存器,正好对应Modbus的四种寄存器类型。
同时可以建立事件:
slave.ModbusSlaveRequestReceived += new EventHandler((obj, o) =>
{
MessageBox.Show("ModbusSlaveRequestReceived:"+ ((IModbusMessage)o.Message).FunctionCode.ToString());
});
有助于快速了解Master端的数据写入,通过FunctionCode枚举查找是进行的哪个寄存器进行的操作,是包括保持寄存器和线圈两种,因为只有这两种寄存器是允许写入的。这样就可以通过事件,处理Master端的操作请求(遍历对应寄存器)。
图16
需要注意的是,除去线圈寄存器外其他寄存器都是16位,所以如果需要表达float类型的数据需要进行数据处理,然后分别以高低位的形式写入到两个寄存器中,同时读取也需要从两个寄存器读取后组合。
相关阅读
Copyright © 2016-2024 JYTEK All Rights Reserved.