Bootstrap

上位机与西门子PLC通信掌握这几点,效率翻倍

今天跟大家分享一下C#上位机与西门子PLC通信时候,必须掌握的几个核心知识点。

掌握这些细节,可能会让你的上位机通信效率翻倍。

我们接下来会通过S7NetPlus通信库的源码来分析。

源码地址:https://github.com/S7NetPlus/s7netplus

一、连接过程

西门子PLC的S7协议是基于TCP/IP来实现的,但是它的连接不是并不是单纯的TCP连接,而是在建立TCP连接(即三次握手)之后,还会有两次握手的过程,第一次握手是为了确认连接,第二次握手是为了设置连接。

图片

图片

二、PDUSize

任何协议都会有一次性读取限制的情况。

比如我们熟悉的Modbus,一次性可以读取125/127个寄存器,三菱PLC一次性可以读取960个字,这个读取限制的值主要由PDU来决定,PDU是Protocol Data Unit的简称。

那么对于西门子PLC来说,它的最大PDUSize是多少呢?

先告诉大家答案,不同型号是不同的,S7-200Smart/S7-1200/S7-300一般是240,S7-400一般是480,S7-1500一般是960。

这个结果是由硬件决定的,我们怎么获取到西门子PLC硬件的PDUSize呢?

其实在我们前面说的第二次握手中,就会返回PDUSize。

图片

我们可以看到,它是由第二次握手返回报文的最后两个字节表示的。

如果不看源码,我们也可以在建立PLC连接之后,通过PLC通信对象的MaxPDUSize属性获取该值。

我们连接不同型号的PLC的输出结果不同,结果如下:

图片

图片

图片

但是MaxPDUSize并不是一次性可以读取的字节数,是报文的长度,实际读取的字节数,需要在此基础上减去包头的18个字节,因此S7-200Smart/S7-1200/S7-300一次可以读取222个字节,S7-400一般是462个字节,S7-1500一般是942个字节。

三、底层分包

刚刚我们提到S7-200Smart一次性只能读取222个字节,但是实际我们在使用S7NetPlus通信库的时候,读取数量却可以超过这个值。

比如我们读取V区从0开始的1000个字节。

图片

我们可以看到是能读取到的,这是什么原因呢?

我们来看下ReadBytes底层的代码:

图片

我们可以看到,这个是通信库底层做了分包处理,比如读取1000个字节,会分成222-222-222-222-112五次去读,然后将结果拼到一起返回,虽然可以读取,但是耗时仍然会是单次通信周期的五倍。

四、通信速率

西门子S7的通信速度跟PLC程序大小,PLC通信负载设置以及PLC型号都有一定关系,我们可以测试一下。

图片

图片

从结果来看,大概在2-10ms之间,不同PLC会有一定区别。S7通信协议并不是最快的,如果我们想要更快的通信效率,可以采用开放式TCP通信。

五、地址不连续

如果我们的下位机地址不连续,我们可能需要分很多组来读取。

S7协议具有这样的优势:S7协议支持一次性读取多个不连续的地址区间。

比如我们想同时读取I区从0开始的10个字节,Q区从0开始的10个字节,M区从0开始的10个字节以及DB1从0开始的10个字节,我们可以这样编写代码:

图片

读取结果如下所示:

图片

List<DataItem>的最大数量是19。由于这种方式,每个Item的参数会消耗一定的报文,因此所有Item的Count之和要比222/462/942更少一些,因为总报文长度仍然受PUDSize的限制。

具体数量是每多一个Item,读取字节数要减少4,对于案例中的4个Item,最多只能读取222-4*(4-1)=210个字节。

六、ReadClass

通信库底层有一个ReadClass方法,这个方法可以直接读取一些连续的变量。

DB1中有很多变量,我们想要读取全部或者部分变量的值,可以使用ReadClass方法:

图片

ReadClass方法的原型如下:

图片

首先,我们需要准备好一个类,这个类必须与DB块变量的类型结构一样,并且顺序也要保持一致:

public class PlcData
{
    public bool InPump1State { get; set; }
    public bool InPump2State { get; set; }
    public bool CirclePump1State { get; set; }
    public bool CirclePump2State { get; set; }
    public bool ValveInState { get; set; }
    public bool ValveOutState { get; set; }
    public bool SysRunState { get; set; }
    public bool SysAlarmState { get; set; }
    public byte[] SpareState { get; set; } = new byte[2];
    public float PressureIn { get; set; }
    public float PressureOut { get; set; }
    public float TempIn1 { get; set; }
    public float TempIn2 { get; set; }
    public float TempOut { get; set; }
    public float PressureTank1 { get; set; }
    public float PressureTank2 { get; set; }
    public float LevelTank1 { get; set; }
    public float LevelTank2 { get; set; }
    public float PressureTankOut { get; set; }
}

    然后直接调用ReadClass方法即可,填入DB号为1,起始地址为0:

      PlcData plcData =siemens.ReadClass<PlcData>( 1, 0);

    我们可以看到,可以直接读到所有变量的值,意味着ReadClass方法中不仅包含了读取,也包含了数据解析的过程。

    图片

    与ReadClass类似的还有一个ReadStruct方法用来读取结构体,大家可以自己研究一下。

    ;