C#读写三菱PLC数据使⽤TCPIP协议
本⽂将使⽤⼀个Github开源的组件库技术来读写三菱PLC和西门⼦plc数据,使⽤的是基于以太⽹的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件⽀持超级⽅便的⾼性能读写操作
github地址:如果喜欢可以star或是fork,还可以打赏⽀持,打赏请认准源代码项⽬。
在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输⼊下⾯的指令安装:
1Install-Package HslCommunication
如果需要教程:Nuget安装教程:
联系作者及加⽅式(激活码在⾥发放):
组件的完整信息和API介绍参照:组件的使⽤限制,更新⽇志,都在该页⾯⾥⾯。
如果你需要在读取PLC数据之后,还要发客户端来实现远程办公室同步监视,可以参考如下的项⽬(基于该组件扩展起来的,带有账户验证,版本控制,数据发,公告管理等等功能)
本⽂将展⽰如何配置⽹络参数及怎样使⽤代码来访问PLC数据,希望给有需要的⼈解决⼀些实际问题。主要对三菱Q系列PLC的X,Y,M,L,B,V,F,S,D,W,R区域的数据读写,对西门⼦PLC的M,Q,I,DB块的数据读写,亲测有效。
此处使⽤了⽹线直接的⽅式,如果PLC接进了局域⽹,就可以进⾏远程读写了^_^
此处使⽤到了2个命名空间:
1 2using HslCommunication;
using HslCommunication.Profinet;
随便聊聊
当我们⼀个上位机需要读取100台西门⼦PLC设备(此处只是举个例⼦,凡是都是使⽤Modbus tcp的都是⼀样的)的时候,你采⽤服务器主动去请求100台设备的机制对性能来说是个极⼤的考验,如果开100个线程去轮询100台设备,那么性能损失将是⾮常⼤的,更不⽤说再增加设备,如果搭建Modbus tcp服务器,就可以完美的解决性能问题,因为连接的压⼒将会平均分摊给每⼀台PLC,服务器端只要新增⼀个时间戳就可以知道客户端有没有连接上。
我们在100台PLC⾥都增加发送Modbus tcp⽅法,将数据发送到服务器的ip和端⼝上去,服务器根据站号来区分设备。这样就可以搭建⼀个⾼性能总站。本组件⽀持快速搭建⼀个⾼性能的Modbus tcp总站。
关于两种模式
在PLC端,包括三菱,西门⼦,欧姆龙以及Modbus Tcp客户端的访问器上,都⽀持两种模式,短连接模式和长连接模式,现在就来解释下什么原理。
短连接:每次读写都是⼀个单独的请求,请求完毕也就关闭了,如果服务器的端⼝仅仅⽀持单连接,那么关闭后这个端⼝可以被其他连接复⽤,但是在频繁的⽹络请求下,容易发⽣异常,会有其他的请求不成功,尤其是多线程的情况下。
长连接:创建⼀个公⽤的连接通道,所有的读写请求都利⽤这个通道来完成,这样的话,读写性能更快速,即时多线程调⽤也不会影响,内部有同步机制。如果服务器的端⼝仅仅⽀持单连接,那么这个端⼝就被占⽤了,⽐如三菱的端⼝机制,西门⼦的Modbus tcp端⼝机制也是这样的。以下代码默认使⽤长连接,性能更⾼,还⽀持多线程同步。
在短连接的模式下,每次请求都是单独的访问,所以没有重连的困扰,在长连接的模式下,如果本次请求失败了,在下次请求的时候,会⾃动重新连接服务器,直到请求成功为⽌。另外,尽量所有的读写都对结果的成功进⾏判断。
关于⽇志记录
不管是三菱的数据访问类,还是西门⼦的,还是Modbus tcp访问类,都有⼀个LogNet属性⽤来记录⽇志,该属性是⼀个接⼝类,ILogNet,凡事继承该接⼝的都可以⽤来记录⽇志,该⽇志会在访问失败时,尤其是因为⽹络的原因导致访问失败时会进⾏⽇志记录(如果你为这个 LogNet 属性配置了真实的⽇志记录器的话):如果你想使⽤该记录⽇志的功能,请参照如下的博客进⾏实例化:
访问测试项⽬
下⾯的⼀个项⽬是这个组件的访问测试项⽬,您可以进⾏初步的访问的测试,免去了您写测试程序的⿇烦,三菱的界⾯和西门⼦的界⾯⼏乎是⼀致的。可以同时参考。该项⽬位于本篇⽂章开始处的Gitbub源代码⾥⾯的
下载地址为:
演⽰项⽬
下⾯的三篇演⽰了具体如何去访问PLC的数据,我们在访问完成后,通常需要进⾏处理,以下的⽰例项⽬就演⽰了后台从PLC读取数据后,前台显⽰并推送给所有在线客户端的功能,客户端并进⾏图形化显⽰,具有⼀定的参考意义,并且推送给⽹页前端,项⽬地址为:
下⾯的图⽚⽰例中的左边程序就是服务器程序,它应该和PLC直接连接并接⼊局域⽹,然后把数据推送给客户端显⽰。注意:⼀个复杂⾼级的程序就应该把处理逻辑程序和界⾯程序分开,⽐如这⾥的服务器程序实现数据采集,推送,存储。让客户端程序去实现数据的整理,分析,显⽰,这样即使客户端程序因为BUG奔溃,服务器端仍然可以正常的⼯作。
测试图⽚
三菱PLC篇(下⾯列举了三种配置⽅法,本组件⽀持⼆进制和ASCII通讯,⽀持1E帧兼容协议访问)
Q06UDV Plc的访问测试感谢:hwdq0012
fx5u plc的访问测试感谢:⼭楂
Q02CPU, L02CPU-CM : 本⼈测试
感谢:⼩懒猪⾬中⼈的测试,VB程序也可以调⽤本通讯库
环境1:此处以GX Works3为⽰例,fx5u的配置如下:(感谢⼭楂提供的图⽚)
环境2:此处以GX Works2为⽰例,测试PLC为L02CPU,内置了以太⽹协议
参数要⼀⼀对应
注意:在PLC的以太⽹模块的配置中,⽆法设置⽹络号为0,也⽆法设置站号为0,所以此处均设置为1,在C#程序中也使⽤上述的配置,在代码中均配置为0,如果您⾃定义设置为⽹络2,站号8,那么在代码中就要写对应的数据。如果仍然通信失败,重新测试0,0。
打开设置:在上图中的打开设置选项,进⾏其他参数的配置,下图只是举了⼀个例⼦,开通了4个端⼝来⽀持读写操作:
端⼝号设置规则:
为了不与原先存在的系统发⽣冲突,您在添加⾃⼰的端⼝时尽量使⽤您⾃⼰的端⼝。
如果读写都需要,尽可能的将读取端⼝和写⼊端⼝区分开来,这样做⽐较⾼性能。
如果您的⽹络状态不是特别稳定,读取端⼝使⽤2个,⼀个受阻切换另⼀个读取可以提升系统的稳定性。
本⽂档仅作组件的测试,所以只⽤了⼀个端⼝作为读写。如果你的程序也使⽤了⼀个端⼝,那么你在读取数据时候,刚好也在写⼊(异步操作可能发⽣这样的情况),那么写⼊会失败!)(在长连接模式下没有这个问题)
三菱PLC的数据主要由两类数据组成,位数据和字数据,在位数据中,例如X,Y,M,L都是位数据,字数据例如D,W。两类的数据在读取解码上存在⼀点⼩差别。(事实上也可以先将16个M先赋值给⼀个D,读取D数据再进⾏解析,在读取M的数量⽐较多的时候,这样操作效率更⾼)
初始化访问PLC对象
注意:如果你想采⽤ASCII来读写数据,请使⽤MelsecMcAsciiNet类,如果想采⽤1E帧协议,使⽤MelsecA1ENet类,除了实例化,其他的数据交互都是⼀样的。
如果想使⽤本组件的数据读取功能,必须先初始化数据访问对象,根据实际情况进⾏数据的填⼊。下⾯仅仅是测试中的数据:
1private MelsecMcNet melsec_net = new MelsecMcNet( "192.168.0.1", 6000 );
如上图所⽰,只要指定了IP地址和端⼝号就完成了初始化的搭建了,当然还⽀持⼀些额外的信息配置
1 2 3melsec_net.ConnectTimeOut = 2000; // ⽹络连接的超时时间melsec_net.NetworkNumber = 0x00;  // ⽹络号
melsec_net.NetworkStationNumber = 0x00; // ⽹络站号
打开连接,并可以判断是否连接上1melsec_net.ConnectClose( );
如果需要判断,那么按照如下的操作
1 2 3 4 5 6 7 8 9OperateResult connect = melsec_net.ConnectServer( ); if(connect.IsSuccess)
{
MessageBox.Show( "连接成功!");
}
else
{
MessageBox.Show( "连接失败!");
}
说明:对象应该放在窗体类下⾯,此处仅仅针对读取⼀台设备的plc,也可以在访问的⽅法中实例化局部对象,初始化数据,然后读取,该对象⼏乎不损耗内存,内存垃圾由CLR进⾏⾃动回收。此处测试⽅便,窗体的多个按钮均连接同⼀台PLC 设备,所以本窗体实例化⼀个对象即可。
关于两种地址的表⽰⽅式
第⼀种,使⽤系统的类来标识,⽐如M200,写成(MelsecDataType.M, 200)的表⽰形式,这样也可以去MelsecDataType⾥⾯到所有⽀持的数据类型。
第⼆种,使⽤字符串表⽰,这个组件⾥所有的读写操作提供字符串表⽰的重载⽅法,所有的⽀持访问的类型对应如下,字符串的表⽰⽅式存在⼗进制和⼗六进制的区别:
输⼊继电器:"X100","X1A0"            // 字符串为⼗六进制机制
输出继电器:"Y100" ,"Y1A0"          // 字符串为⼗六进制机制
内部继电器:"M100","M200"          // 字符串为⼗进制
锁存继电器:"L100"  ,"L200"          // 字符串为⼗进制
报警器:      "F100", "F200"            // 字符串为⼗进制
边沿继电器:"V100" , "V200"          // 字符串为⼗进制
链接继电器:"B100" , "B1A0"          // 字符串为⼗六进制
步进继电器:"S100" , "S200"          // 字符串为⼗进制
数据寄存器:"D100", "D200"          // 字符串为⼗进制
链接寄存器:"W100" ,"W1A0"        // 字符串为⼗六进制
⽂件寄存器:"R100","R200"            // 字符串为⼗进制
展⽰⼀些简单实⽤基础数据读写,这些数据的读写没有进⾏严格的是否成功判断(判断⽅法参照后⾯的代码),⼀般⽹络良好的情况下都会成功,但不排除失败,以下代码仅作测试,所有没有严格判断是否成功:
1
2 3 4 5 6 7 8bool[] M100 = melsec_net.ReadBool("M100",1).Content;            // 读取M100是否通,⼗进制地址
bool[] X1A0 = melsec_net.ReadBool("X1A0",1).Content;            // 读取X1A0是否通,⼗六进制地址
bool[] Y1A0 = melsec_net.ReadBool("Y1A0",1).Content;            // 读取Y1A0是否通,⼗六进制地址
bool[] B1A0 = melsec_net.ReadBool("B1A0",1).Content;            // 读取B1A0是否通,⼗六进制地址
short short_D1000 = melsec_net.ReadInt16("D1000").Content;  // 读取D1000的short值  ,W3C0,R3C0 效果是⼀样的ushort ushort_D1000 = melsec_net.ReadUInt16("D1000").Content; // 读取D1000的ushort值
tcpip协议是最基本的协议int int_D1000 = melsec_net.ReadInt32("D1000").Content;          // 读取D1000-D1001组成的int数据
uint uint_D1000 = melsec_net.ReadUInt32("D1000").Content;      // 读取D1000-D1001组成的uint数据

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。