因业务须要了解Modbus协议的使用,所以对Modbus的协议,以及相应的C#处理应用进行了解,针对协议的几种方式(RTU、ASCII、TCPIP)进行了封装,以及对Modbus的各类功能码的特色进行了详细的了解,本篇随笔基于这些知识进行了必定的梳理和介绍,主要内容包括Modbus协议简要介绍、Modbus模拟工具使用和Modbus应用开发几个部分。数据库
Modbus 协议是应用于电子控制器上的一种通用语言。经过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间能够通讯。它已经成为一通用工业标准。有了它,不一样厂商生产的控制设备能够连成工业网络,进行集中监控。服务器
此协议定义了一个控制器能认识使用的消息结构,而无论它们是通过何种网络进行通讯的。它描述了一控制器请求访问其它设备的过程,若是回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。 网络
当在一Modbus网络上通讯时,此协议决定了每一个控制器需要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。若是须要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。ide
Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP。函数
Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。工具
标准的Modbus协议物理层接口有RS23二、RS42二、RS485和以太网接口,采用master/slave方式通讯。学习
对于串行链接,存在两个变种,它们在数值数据表示不一样和协议细节上略有不一样。Modbus RTU是一种紧凑的,采用二进制表示数据的方式,Modbus ASCII是一种人类可读的,冗长的表示方式。这两个变种都使用串行通讯(serial communication)方式。RTU格式后续的命令/数据带有循环冗余校验的校验和,而ASCII格式采用纵向冗余校验的校验和。被配置为RTU变种的节点不会和设置为ASCII变种的节点通讯,反之亦然。
对于经过TCP/IP(例如以太网)的链接,存在多个Modbus/TCP变种,这种方式不须要校验和计算。
对于全部的这三种通讯协议在数据模型和功能调用上都是相同的,只有封装方式是不一样的。
Modbus有一个扩展版本Modbus Plus(Modbus+或者MB+),不过此协议是Modicon专有的,和Modbus不一样。它须要一个专门的协处理器来处理相似HDLC的高速令牌旋转。它使用1Mbit/s的双绞线,而且每一个节点都有转换隔离装置,是一种采用转换/边缘触发而不是电压/水平触发的装置。链接Modbus Plus到计算机须要特别的接口,一般是支持ISA(SA85),PCI或者PMCIA总线的板卡。测试
MODBUS 协议定义了一个与基础通讯层无关的简单协议数据单元(PDU)。this
Modbus串行连路上的的PDU以下所示。spa
错误检验域是对报文内容执行"冗余校验" 的计算结果。根据不一样的传输模式(RTU or ASCII) 使用两种不一样的计算方法。
RTU的报文格式以下所示。
ASCII码的报文格式以下所示。
在 ASCII 模式, 报文用特殊的字符区分帧起始和帧结束。一个报文必须以一个‘冒号’ ( : ) (ASCII 十六进制3A )起始,以‘回车-换行’ (CR LF) 对(ASCII 十六进制0D 和0A) 结束。
而Modbus TCP数据帧包含报文头、功能代码和数据3部分。
MBAP Header长度共7个字节,分别为Transaction identifier(事务标识符),Protocol identifier(协议标识符),Length(长度),
Unitidentifier(单元标识符)组成,具体以下表所示:
请求和响应带有六个字节的前缀,以下:
byte 0: 事务处理标识符 –由服务器复制 –一般为 0
byte 1: 事务处理标识符 –由服务器复制 –一般为 0
byte 2: 协议标识符= 0
byte 3: 协议标识符= 0
byte 4: 长度字段 (上半部分字节) = 0 (全部的消息长度小于256)
byte 5: 长度字段 (下半部分字节) = 后面字节的数量
byte 6: 单元标识符 (原“从站地址”)
byte 7: MODBUS 功能代码
byte 8 on: 所需的数据
数据区:数据区是根据不一样的功能码而不一样。数据区能够是实际数值、设置点、主机发送给从机或从机发送给主机的地址。
标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通信采用Maser/Slave方式。
Modbus协议须要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验.
ModbusTCP模式没有额外规定校验,由于TCP协议是一个面向链接的可靠协议。
对于常规的Modbus串口协议,咱们来看看03功能码的读取寄存器的操做请求和响应代码了解下。
请求PDU格式以下所示。
响应的PDU格式以下所示。
一个请求读寄存器108-110 的实例:
能够注意到,不少数据的处理,须要拆分高位低位,高位在前,低位在后的模式。
根据这些RTU、ASCII、TCPIP的Modbus协议的不一样,咱们能够构建一个通用的处理程序来处理这些操做,在后面的应用开发部分继续介绍。
通常在作Mobus前期的开发的时候,通常不是针对具体的Modbus设备进行寄存器的处理,而是使用Modbus模拟工具来进行调试,通常咱们须要配合Modbus Slave、Modbus Poll、Virtual Serial Port Driver这几个模拟软件来进行开发的。
Modbus Poll :Modbus主机仿真器,用于测试和调试Modbus从设备。该软件支持Modbus的RTU、ASCII、TCP/IP。用来帮助开发人员测试Modbus从设备,或者其它Modbus协议的测试和仿真。
Modbus Slave: Modbus从设备仿真器,能够仿真32个从设备/地址域。每一个接口都提供了对EXCEL报表的OLE自动化支持。主要用来模拟Modbus从站设备,接收主站的命令包,回送数据包。帮助Modbus通信设备开发人员进行Modbus通信协议的模拟和测试,用于模拟、测试、调试Modbus通信设备。
Virtual Serial Port Driver:虚拟串口工具,不须要串口接线,提供虚拟的串口,适合学习和调试使用。
配合这几款软件,咱们就能够实现串口Modbus协议的模拟测试了,若是咱们使用Modbus的TCPIP协议,那么咱们不须要VSPD也能够。
若是咱们使用Modbus协议的串口通信方式,那么咱们先要使用VSPD进行串口的配对模拟,模拟出两个通信的串口端口,端口配对模拟成功后,咱们能够看到设备管理器中增长了两个端口了。
接着使用从机模拟器,模拟一个Modbus从机供测试,经过菜单【Connection】【Connect】启动,咱们选择链接方式为串口,端口则选择咱们配对的其中一个端口便可,以下图所示。
其中模式选择RTU或者ASCII均可以,这两个模式协议有所不一样,一旦从机选择RTU模式,那么Modbus主机也须要选择对应的RTU模式,反之亦然。
其余串口设置,如波特率、数据位、奇偶位、中止位等默认配置便可。
若是咱们选择TCPIP模式,那么对应Modbus主机也须要选择TCPIP方式。
一旦Modbus从机启动,就会处理来自Modbus主机的指令请求(若是有的话),并作相应处理,咱们能够经过【Display】【Communication】菜单弹出的对话框,了解到对应请求和应答的协议详细信息。
Modbus主机的启动和ModBus从机相似,咱们根据ModBus从机的配置,选择对应的主机配置,Modbus模拟主机启动和查看通信记录界面以下所示。
另外咱们能够经过【Display】里面选择内容显示的进制格式。
在从机的设置里面,咱们能够修改从机的定义信息,以便设置对应的从机ID,功能码,其实地址,长度或者数量的信息,以下界面所示。
咱们能够根据实际的寄存器地址和数量,设置对应的数值,以下是显示4个数据的内容设置和显示内容。
设置后正常的内容显示以下。
同时咱们也须要设置对应Modbus主机模拟器的地址和数量,正确设置后能够正常显示。
经过更深一步的设置或者调整,咱们能够极大程度的进行模拟Modbus实际设备的处理方式,从而在没有实际Modbus硬件设备的状况下尽量经过前期的模拟完成常规功能的测试和准备。
在咱们开发Modbus应用的时候,咱们对照相应的主从机Modbus协议请求和应答,可以检查咱们程序的输出是否正常,从而能够快速的开发Modbus的应用处理功能。
为了模拟对接Modbus的RTU、ASCII、TCP/IP协议处理,我根据不一样协议的处理方式定义了一个辅助函数,而后统一进行处理,以便达到统一调用的处理便利。
首先咱们来看看使用串口模式下(RTU、ASCII)的处理界面效果,这个直接获取模拟器Modbus Slave从机的数值进行显示的。
TCPIP网络方式对接Modbus界面处理效果以下所示。
二者数据均来源于Modbus Slave从机的数值,只是它们对接的方式不一样。
串口的处理,我经过SerialPortUtil类来使用Windows的串口类,处理对应的串口操做,经过定义事件的方式,使得串口收到数据的时候,及时通知调用者进行界面更新处理便可。
//使用字符串参数构造 serial = new SerialPortUtil(portname, this.txtBaudRate.Text, this.txtParity.Text, this.txtDataBits.Text, this.txtStopBit.Text); //收到数据处理的事件 serial.DataReceived += Serial_DataReceived; serial.RTUMode = this.radRTU.Checked;//默认RTU模式为True,不然使用ASCII模式
收到数据后,及时经过委托方式,通知UI进行界面的更新显示。
/// <summary> /// 收到串口响应事件后,及时进行处理(更新在界面上) /// </summary> /// <param name="e"></param> private void Serial_DataReceived(DataReceivedEventArgs e) { //记录在日志,方便复制 LogTextHelper.Info(e.DataReceived); //使用委托进行处理界面控件的数据更新 this.txtResponse.Invoke(new MethodInvoker(()=> { //显示在界面上 this.txtResponse.AppendText(e.DataReceived); this.txtResponse.AppendText(Environment.NewLine); var dataBytes = e.BytesReceived; if(dataBytes != null && dataBytes.Length > 2) { var function = dataBytes[1]; if(function > 0x80)//128 { //Modbus的异常代码大于128,若是是异常,则能够解析错误 var newFunction = function - 0x80; lblTips.Text = "响应有异常,功能代码:" + newFunction.ToString("D2"); lblTips.Text += ",错误描述:" + ((ModBusExceptionCode)newFunction).ToString(); } else { lblTips.Text = "响应正常";//小于128的为正常响应 } } })); }
而对于网络方式,咱们先要定义一个Socket通信的基类,封装相关的通信处理操做。
而后简单构建一个子类进行使用,以下所示。
/// <summary> /// 通讯类子类 /// </summary> public class ModbusClient : BaseSocketClient { public ModbusClient() { this.Name = "ModbusClient"; } }
界面处理的时候,咱们只须要初始化一个ModbusClient类来使用便可,以下代码所示。
client = new ModbusClient(); //收到数据处理的事件 client.DataReceived += Client_DataReceived;
收到数据通知界面进行更新的操做以下所示。
private void Client_DataReceived(DataReceivedEventArgs e) { //记录在日志,方便复制 LogTextHelper.Info(e.DataReceived); //使用委托进行处理界面控件的数据更新 this.txtResponse.Invoke(new MethodInvoker(() => { this.txtResponse.AppendText(e.DataReceived); this.txtResponse.AppendText(Environment.NewLine); var dataBytes = e.BytesReceived; if (dataBytes != null && dataBytes.Length > 2) { //串口功能码为第二个字节,TCP/IP功能码为第8个 var function = dataBytes[7]; if (function > 0x80)//128 { //Modbus的异常代码大于128,若是是异常,则能够解析错误 var newFunction = function - 0x80; lblTips.Text = "响应有异常,功能代码:" + newFunction.ToString("D2"); lblTips.Text += ",错误描述:" + ((ModBusExceptionCode)newFunction).ToString(); } else { lblTips.Text = "响应正常";//小于128的为正常响应 } } })); }
不论是串口的RTU或者ASCII,又或者是TCPIP的协议,咱们能够经过定义一个协议封装的辅助类ModbusQueryHelper来处理协议的具体细节。
/// <summary> /// Modbus查询消息生成辅助类,能够用于串口RTU/ASCII协议,也能够用于TCPIP协议。 /// 用于生成各类功能代码的消息内容。 /// </summary> public class ModbusQueryHelper { /// <summary> /// 是否为RTU模式,默认为True,不然为ASCII方式 /// </summary> public ModbusProtocol Protocol { get; set; } = ModbusProtocol.RTU; /// <summary> /// 默认函数 /// </summary> public ModbusQueryHelper() { } /// <summary> /// 参数化构造,指定RTU模式 /// </summary> /// <param name="protocal">Modbus协议:ASCII,RTU, TCP,默认为RTU</param> public ModbusQueryHelper(ModbusProtocol protocal) { this.Protocol = protocal; }
而其中ModbusProtocol是一个枚举,定义以下所示。
/// <summary> /// 几种经常使用的Modbus协议 /// </summary> public enum ModbusProtocol { /// <summary> /// 串口的ASCII模式 /// </summary> ASCII, /// <summary> /// 串口的RTU模式 /// </summary> RTU, /// <summary> /// 网络TCPIP模式 /// </summary> TCP }
咱们经过ModbusQueryHelper 类,能够处理不一样协议之间的封装细节,并能够对各类功能码的协议进行封装处理。
以上就是 相关Modbus的应用处理和封装,对于常规的Modbus协议能够极大简化对接处理,在实际对接Modbus设备的时候,咱们只须要根据对应的说明书,获取对应的内容,就能够把例如温度、湿度、转速等一些设备或者机器人的参数得到,并记录在数据库里面,而后在应用模块中整合一些图表展现就能够很好的实现看板功能了。