一般在做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的应用处理功能。
3)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设备的时候,我们只需要根据对应的说明书,获取对应的内容,就可以把例如温度、湿度、转速等一些设备或者机器人的参数获得,并记录在数据库里面,然后在应用模块中整合一些图表展示就可以很好的实现看板功能了。
专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
转载请注明出处:撰写人:伍华聪 http://www.iqidi.com
标签: modbus开发