• 教大家使用C#如何方便快速的读写西门子DB块的值,实现上位机跟plc进行通讯的流程
  • 使用的西门子PLC型号,S7 1200 

1.Nuget 安装s7 驱动包

 2.西门子plc定义一个db块,这个是我们上位机要读写的DB

 3. 根据西门子S7 协议文档

读取DB块的方式有很多种,直接根据该DB的偏移量进行读写也行,但读或写数据都需要进行对应的数据类型转换才能最终拿到值或写进去,个人觉得太麻烦了。不过文档也提供了一种比较简单的方式,就是读写类的方式。

3.1 读写类的原理,是通过反射处理的,不过我们并不需要关心它是如何反射的,我们只关心我们要如何使用。

3.2 首先,需要建立一个类,跟西门子 DB块 名称一样的实体类

西门子plc建立的DB 块名称 

 C# 建立对应的db块名称的类属性

 4. 类建立完成,我们通过接口的方式实现读写数据

接口定义两种,普通接口和泛型接口。后面会介绍使用泛型接口和普通接口的区别以及好处

4.1 普通接口的实现

 根据s7 文档,提供了ReadClass读取的方法。提供读取的方法有很多种,我们只介绍一种方式,就是通过传入建立好的实体类。其他自行研究实现。

 public void ReadClass( object sourceClass, int db, int startByteAdr = 0)
  • sourceClass 分配值的类实例
  • db 数据块
  • startByteAdr 读取字体起始地址

 4.2 我们知道该方法的传入参数后,接着需要定义接口的方法。

 为什么要定义返回一个实体,因为我们读数据时,需要传入一个实体,读取结束后,Plc的Db块的值就会反射在这个实体中,我们通过这个实体取到最新读到的数值。接下来我们实现这个接口方法。

 1.上面的实现方式,它是等同于下面的书写方式。

CathodeEntity testClass = new CathodeEntity();
plc.ReadClass(testClass, 5);

  只不过,我为了防止每一次调用都会重新new 一个CathodeEntity类。我就把这个 CathodeEntity 类设计成单例模式了。永远只实例化一次,我们需要修改的只是这个类的属性值而已。后面的5,意思就我们要读的DB块5

 2.单列实例类 CathodeEntity

public class CathodeEntity
    {
        public bool CathodeUp { get; set; }
        public bool EntryAllowed { get; set; }
        public bool LeaveAllowed { get; set; }
        // 定义一个静态变量来保存类的实例
        private static CathodeEntity uniqueInstance;
        // 定义一个标识确保线程同步
        private static readonly object locker = new object();
        // 定义私有构造函数,使外界不能创建该类实例
        private CathodeEntity()
        {

        }
        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static CathodeEntity GetInstance()
        {
            // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
            // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
            // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
            if (uniqueInstance == null)
            {
                lock (locker)
                {
                    // 如果类的实例不存在则创建,否则直接返回
                    if (uniqueInstance == null)
                    {
                        uniqueInstance = new CathodeEntity();
                    }
                }
            }
            return uniqueInstance;
        }
    }

3.单例设计模式参考 

4.3 接下来,调用该接口,就能获取到plc 的值了。

 读取到的值跟西门子PLC DB5的值是一样的

 5. 以上就是普通接口的实现方式,但如果我们是多个db块,按照常规是不是得写N个实现方法,这样不仅不容易维护,代码量也多。所以我们把接口设计成泛型的实现。

为什么能设计泛型,根据这个s7 协议,读类的方法,第一个参数是object 类型,也可以是具体的实例类型。

5.1 我们要设定接口约束类型。为什么要这样干,就是我们要让实现这个接口的类,规定它这个T类型,你不能传int 或string等,必须传个类给它。这样就防止,接口实现的时候,你传错参数了。

 5.2 接着,我们实现这个泛型接口,那么这个t,我们就可以换成,我们跟Plc定义的数据块,建立的对应实体类了。

例如,当前Plc有一个db块5,我们建立对应的实体属性类名是CathodeEntity,我们调用的时候就传CathodeEntity 实体进来。如果plc 有多个db块,我们也要有多个对应的实体类。这个时候我们就可以把不同的实体类传进去,返回来就是plc 具体db块的值了。

实现接口 

 5.3 调用接口。首先实例化CathodeEntity 实体类,把CathodeEntity传进去,得到的就是对应Db块5的值。如果是其他Db块,就需要建立跟plc db块名称对应的实体类,然后再CathodeEntity 换成要新建读取的类。这样就实现的接口方法复用。

 6. 接口调用具体实现。

 6.1 Plc 连接类,因为我只需要实例化一次,所以也要把它设计成了单例模式

    /// <summary>
    /// 正极缓存货架plc
    /// </summary>
    internal class CathodeBufferRack
    {
        //定义静态变量保存实例
        public static volatile Plc CathodePlc;
        //定义一个标识确保线程同步
        private static object lockHelper = new object();
        //定义私有变量,使外部不能创建该类实例
        private CathodeBufferRack()
        {

        }
        public static Plc Instance(string DeviceIp)
        {
            if(CathodePlc == null)
            {
                lock (lockHelper)
                {
                    if (CathodePlc == null)
                    {
                        CathodePlc = new Plc(CpuType.S71200, DeviceIp, 0,1);
                        CathodePlc.Open();
                        if (!CathodePlc.IsConnected)
                        {
                            CathodePlc.Close();
                            CathodePlc = null;
                        }
                    }
                }
            }
            return CathodePlc;
        }
    }

 7. 写plc db也同样的道理。

7.1 定义写的接口方法

7.2 实现接口方法

 7.3 程序调用

7.4  LeaveAllowed 实体更改成true后,传进去。plc驱动程序自动反射,把对应实体plc db块的值直接更新了。这样就不用通过偏移量来操作读写db块了。

如果db块有不同的类型,例如byte ,Int ,DInt ,等,同样的道理,创建对应的数据类型就可以了。

 7.5 PLC与C# 常用数据类型转换

PLCC#
BoolBool
Wordushort
Intushort
Dworduint32
Dintuint
bytebyte
Real
bouble

具体使用参考官网

8. 普通接口和泛型接口对比。

8.1 如下例子所示,普通接口固定死传入实体参数后,它就只能规定你传入CathodeEntity实体类对象。

普通接口方法方法:

但如果,我们要读同一个plc,不同的db块,是不是这个方法就不适用了。那我们得要再重新定义一个接口方法,只是类参数不一样。

但plc.WriteClass()这个方法是适用写不同的db块的,它是由传入的参数决定它要写那个db块。所以这样子重复的做接口实现,增加代码量而且还不容易维护。


 所以,我们要学会分析构思代码。

1.首先,得分析这个plc.WriteClass()能接受的参数类型是什么。它的第一个参数是object 类型,就是任意类数据类型。(当然了,这个的任意类型是有限制的,因为它是plc 驱动程序提供的写类的方法,它的任意数据类型就只能限制于类),就是说可以传不同的类给它

2.那我们清楚它接受数据类型后,接着是不是要考滤接口的设计实现。因为它规定了,只能传入任意的 类型,那也就是,它不能接受int或string 类型了。所以我们定义泛型接口要约束这个泛型接口类型。泛型接口的实现,只能传进来类对象,不能是其他。

3.所以泛型接口实现,传入一个obj

4.然后接口调用的时候,传什么,对应的泛型就是什么

 总结:这样减少了代码量,容易维护。

那就先这样吧,理解了点赞收藏啊,防止那天找不到了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐