S7协议(通用写法)

发布时间 2023-11-26 18:17:58作者: 峰永

  S7 通信协议是西门子公司推出的一种工业通信协议,用于在工业自动化领域中实现设备之间的通信。它是一种基于以太网的协议,支持多种通信方式,如 TCP/IP、UDP/IP、ISO-on-TCP 等。

主要用于西门子 S7 系列 PLC(可编程逻辑控制器)之间的通信,也可以用于与其他设备之间的通信,如人机界面(HMI)、变频器、传感器等。通过 S7 通信协议,可以实现数据的读取、写入、传输和处理等功能,从而实现设备之间的互联互通。具有高效、可靠、安全等特点,广泛应用于工业自动化领域。同时,西门子公司还提供了丰富的工具和软件,方便用户进行 S7 通信协议的开发和应用。
  
  使用西门子提供的工具和软件进行 S7 通信协议的开发和应用,一般需要以下步骤:
  1. 安装西门子提供的工具和软件:首先需要安装西门子提供的相关工具和软件,如 TIA Portal(博途)、STEP 7 等。这些工具和软件可以帮助用户进行 S7 通信协议的配置、编程和调试。
  2. 配置通信模块:在进行 S7 通信协议的开发和应用之前,需要先配置通信模块,如以太网模块、PROFIBUS 模块等。在配置通信模块时,需要设置模块的参数,如 IP 地址、子网掩码、网关等。
  3. 编写通信程序:在配置好通信模块后,可以使用西门子提供的编程语言,如 LAD(梯形图)、FBD(功能块图)、STL(语句表)等,编写通信程序。在编写通信程序时,需要使用 S7 通信协议提供的指令和功能块,如 READ、WRITE、TRANSMIT、RECEIVE 等。
  4. 调试通信程序:在编写好通信程序后,可以使用西门子提供的调试工具,如 PLCSIM(仿真软件)等,对通信程序进行调试。在调试过程中,可以查看通信数据的传输情况,以及检查程序是否存在错误。
  5. 应用通信程序:在调试通过后,可以将通信程序下载到实际的设备中,进行实际的通信应用。在应用通信程序时,需要注意设备之间的连接方式、数据格式等,以确保通信的稳定性和可靠性。
  开发语言使用C#,开发工具为VS2022,只涉及到后端代码,不包含前端页面。
      1、安装nuget包(s7netplus),支持S7200、Logo0BA8、S7200Smart、S7300、S7400、S71200、S71500
 
  
  2、 S7通信类(S7支持多种读写方式,例如Bytes、Class、Struct等),下面是优化过的部分读写方式
查看代码
using S7.Net;
using S7.Net.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using static S7Communication.S7Enums;
using DateTime = System.DateTime;

namespace S7Communication
{
    public class S7CommunicationHelp
    {
        //限制长度
        private const int LimitReadDataItemsCount = 18;

        public static S7CommunicationHelp Instance;

        private S7CommunicationHelp()
        {
            Instance = new S7CommunicationHelp();
        }

        /// <summary>
        /// 定义一个plc
        /// </summary>
        private Plc _plc;

        /// <summary>
        ///连接
        /// </summary>
        public bool IsConnected = false;

        /// <summary>
        /// 连接PLC
        /// </summary>
        /// <param name="type">PLC CPU类型</param>
        /// <param name="IP">PLC服务器IP</param>
        /// <param name="Rack">机台号</param>
        /// <param name="Slot">插槽号</param>
        /// <returns></returns>
        public BaseResult PLCConnect(PLC_CPUType type, string IP, short Rack, short Slot)
        {
            var cpuType = (CpuType)Enum.Parse(typeof(CpuType), type.ToString());
            //实例化
            _plc = new Plc(cpuType, IP, Rack, Slot);
            IsConnected = false;
            try
            {
                _plc.Open();
                IsConnected = _plc.IsConnected;
                LogHelp.AddLog<InfoLogEntity>(IsConnected ?
                                  $"TCP服务器 IP:{IP} 机台号{Rack} 插槽号{Slot} 连接成功! " :
                                  $"TCP服务器 IP:{IP} 机台号{Rack} 插槽号{Slot} 连接失败! ");
            }
            catch (Exception ex)
            {
                LogHelp.AddLog<InfoLogEntity>($"PLC连接失败:{ex.Message} ");
            }

            return IsConnected;
        }

        /// <summary>
        /// 断开PLC
        /// </summary>
        public void PLCDisConnect()
        {
            if (_plc != null)
            {
                _plc.Close();
                LogHelp.AddLog<InfoLogEntity>($"PLC服务器断开连接! ");
            }
        }

        /// <summary>
        /// 按照DataItem方式读取PLC数据
        /// </summary>
        /// <param name="dataItem"></param>
        /// <param name="type">Plc类型</param>
        public void PLC_Read(DataItem dataItem)
        {
            var type = S7DBAttribute.GetPlcType(dataItem.DB);
            var lis = new List<DataItem> { dataItem };
            _plc.ReadMultipleVars(lis);
        }

        /// <summary>
        /// 根据Lambda表达式读取Plc
        /// </summary>
        public F PLC_Read<T, F>(Expression<Func<T, F>> expression) where T : BaseDB where F: struct   
        {
            var dataItem = S7DataItemHelp.GetReadDataItem(expression);
            var lis = new List<DataItem> { dataItem };
            var plcType = S7DBAttribute.GetPlcType(dataItem.DB);
            _plc.ReadMultipleVars(lis);

            var f = (F)lis[0].Value;
            return f;
        }


        /// <summary>
        /// 开始读取
        /// </summary>
        /// <param name="dictDataItems">内容</param>
        /// <param name="sleepTimes"></param>
        public void StartRead(Dictionary<string, DataItem> dictDataItems, int sleepTimes = 300)
        {
            var type = S7DBAttribute.GetPlcType(dictDataItems.First().Value.DB);
            //备份的上次记录
            var dictDataItemsBackup = DeepCopy.DeepCopyByJson(dictDataItems);
            var lisDataItems = dictDataItems.Values.ToList();
            Task.Factory.StartNew(() =>
            {
                bool t = false;
                while (true)
                {
                    if (t)
                    {
                        Thread.Sleep(sleepTimes);
                        continue;
                    }

                    var start1 = DateTime.Now;
                    //单次同时读Plc有限制,超过限制得分多次读
                    if (lisDataItems.Count > LimitReadDataItemsCount)
                    {
                        var tempList = new List<DataItem>();
                        int count = lisDataItems.Count / LimitReadDataItemsCount;
                        for (var j = 0; j <= count; j++)
                        {
                            var items = lisDataItems.Skip(j * LimitReadDataItemsCount).Take(LimitReadDataItemsCount)
                                .ToList();
                            if (items.Count <= 0)
                                continue;
                            _plc.ReadMultipleVars(items);
                            tempList.AddRange(items);
                        }

                        lisDataItems = tempList;
                    }
                    else
                    {
                        _plc.ReadMultipleVars(lisDataItems);
                    }

                    var start2 = DateTime.Now;
                    var dictDataItemsTemp =
                        new Dictionary<string, DataItem>(lisDataItems.Count);
                    int i = 0;
                    foreach (var keyValuePair in dictDataItemsBackup)
                    {
                        try
                        {
                            DataItem currentDataItem = lisDataItems[i];
                            dictDataItemsTemp.Add(keyValuePair.Key, currentDataItem);
                            //不变更的属性不发布
                            if (Equals(keyValuePair.Value.Value, currentDataItem.Value))
                            {
                                continue;
                            }
                        }
                        catch (Exception e)
                        {
                            //to do 
                        }
                        finally
                        {
                            i++;
                        }
                    }

                    //保留备份数据
                    dictDataItemsBackup.Clear();
                    dictDataItemsBackup = DeepCopy.DeepCopyByJson(dictDataItemsTemp);

                    var ts1 = start2 - start1;
                    var ts2 = DateTime.Now - start2;
                    Thread.Sleep(20);

                }
            });
        }


        /// <summary>
        /// 通过初始偏移量试出DB块长度
        /// </summary>
        /// <param name="db">数据块地址</param>
        /// <param name="startByteAdr">建议设置为db块的估计长度,这样试探的次数最小</param>
        /// <param name="type">Plc类型</param>
        /// <returns></returns>
        public int PLC_ReadDbLength(int db, int startByteAdr)
        {
            var type = S7DBAttribute.GetPlcType(db);
            int? dbLength = S7DataItemHelp.GetDbLength(db);
            if (dbLength != null)
            {
                return dbLength.Value;
            }

            var catchError = false;
            var readSuccess = false;
            dbLength = 0;
            while (startByteAdr >= 0)
            {
                try
                {
                    var result =  _plc.ReadBytes(S7.Net.DataType.DataBlock, db, startByteAdr, 1);
                    if (catchError)
                    {
                        dbLength = startByteAdr + 1;
                        break;
                    }

                    readSuccess = true;
                    startByteAdr++;
                }
                catch (Exception)
                {
                    if (readSuccess)
                    {
                        dbLength = startByteAdr;
                        break;
                    }

                    startByteAdr--;
                    catchError = true;
                }
            }

            //记住这个值,避免反复试探
            S7DataItemHelp.SetDBValue(db, dbLength.Value);

            return dbLength.Value;
        }

    }
}

  3、获取DB块中的地址以及偏移量

查看代码
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace S7Communication
{
    public static class S7AttributeHelp
    {
        /// <summary>
        /// 获取数据块的地址
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static int GetDB<T>() where T : BaseDB
        {
            Type type = typeof(T);
            S7DBAttribute sprayingDb = (S7DBAttribute)Attribute.GetCustomAttribute(type, typeof(S7DBAttribute));
            if (sprayingDb == null)
            {
                throw new Exception($"{type.FullName}  没有设置DB标签");
            }
            return sprayingDb.DB;
        }

        /// <summary>
        /// 获取数据块的地址
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static int GetDB(Type type)
        {
            S7DBAttribute sprayingDb = (S7DBAttribute)Attribute.GetCustomAttribute(type, typeof(S7DBAttribute));
            if (sprayingDb == null)
            {
                throw new Exception($"{type.FullName}  没有设置DB标签");
            }
            return sprayingDb.DB;
        }

        /// <summary>
        /// 获取在DB块里面的位置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static (int StartByteAdr, Byte BitAdr) GetAdress<T>(string name) where T : BaseDB
        {
            Type type = typeof(T);
            PropertyInfo propertyInfo = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
            if (propertyInfo == null)
            {
                throw new Exception($"{type.FullName}  的 {name}  属性不存在");
            }
            S7PropertyAttribute s7Property = (S7PropertyAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(S7PropertyAttribute));
            if (s7Property == null)
            {
                throw new Exception($"{type.FullName}  没有设置DB标签");
            }
            return (s7Property.StartByteAdr, s7Property.BitAdr);
        }

        /// <summary>
        /// 获取在DB块里面的位置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static (int StartByteAdr, Byte BitAdr) GetAdress(Type type, string name)
        {
            PropertyInfo propertyInfo = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
            if (propertyInfo == null)
            {
                throw new Exception($"{type.FullName}  的 {name}  属性不存在");
            }
            S7PropertyAttribute s7Property = (S7PropertyAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(S7PropertyAttribute));
            if (s7Property == null)
            {
                throw new Exception($"{type.FullName}  没有设置DB标签");
            }
            return (s7Property.StartByteAdr, s7Property.BitAdr);
        }
    }
}

  4、利用特性读取数据块地址、开始位置、偏移量等信息

查看代码
/// <summary>
    /// S7数据块特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
    public class S7DBAttribute : Attribute
    {
        /// <summary>
        /// 数据块地址
        /// </summary>
        public int DB;
        /// <summary>
        /// 存放所有数据块地址和Plc类型对应关系
        /// </summary>
        public static Dictionary<int, PlcPropertyType> S7DBDictionary = new Dictionary<int, PlcPropertyType>();

        /// <summary>
        /// 根据DB获取对应的DB类型
        /// </summary>
        /// <param name="db">数据块地址</param>
        /// <returns></returns>
        public static PlcPropertyType GetPlcType(int db)
        {
            return S7DBDictionary.ContainsKey(db) ? S7DBDictionary[db] : PlcPropertyType.MainPlc;
        }

        private PlcPropertyType _plcType = PlcPropertyType.MainPlc;

        /// <summary>
        /// Plc类型
        /// </summary>
        public PlcPropertyType PlcType
        {
            get
            {
                return _plcType;
            }
            set
            {
                _plcType = value;
                if (!S7DBDictionary.ContainsKey(DB))
                {
                    S7DBDictionary[DB] = _plcType;
                }
            }
        }
    }

    /// <summary>
    /// DB块字段属性
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class S7PropertyAttribute : Attribute
    {
        /// <summary>
        /// 起点地址
        /// </summary>
        public int StartByteAdr;

        /// <summary>
        /// 偏移量
        /// </summary>
        public byte BitAdr;
    }
}

  5、编写用于写入的DB类

查看代码
 /// <summary>
    /// 风扇
    /// </summary>
    [S7DB(DB = 706)]
    public class FanDB_R : BaseDB
    {
        /// <summary>
        /// 风扇1功率百分比值(前)
        /// </summary>
        [S7Property(StartByteAdr = 0, BitAdr = 0)]
        public float FanPowerFront { get; set; }

        /// <summary>
        /// 风扇2功率百分比值(后)
        /// </summary>
        [S7Property(StartByteAdr = 4, BitAdr = 1)]
        public float FanPowerRear { get; set; }
     
    }

    6、编写用于读取的DB类

查看代码
/// <summary>
    /// 风扇
    /// </summary>
    [S7DB(DB = 706)]
    public class FanDB_R : BaseDB
    {
        /// <summary>
        /// 风扇1功率百分比值(前)
        /// </summary>
        [S7Property(StartByteAdr = 0, BitAdr = 0)]
        public float FanPowerFront { get; set; }

        /// <summary>
        /// 风扇2功率百分比值(后)
        /// </summary>
        [S7Property(StartByteAdr = 4, BitAdr = 1)]
        public float FanPowerLeftRear { get; set; }
    }

  7、编写操作类

查看代码
 using S7.Net.Types;
using S7Communication.DBData;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace S7Communication
{
    public class FanViewModel
    {
        public static FanViewModel Instance=new FanViewModel();

        private FanViewModel()
        {
            //读取逻辑
        }

        /// <summary>
        /// 风扇1功率百分比值(左前)
        /// </summary>
        public float FanPowerFront { get; set; }

        /// <summary>
        /// 风扇2功率百分比值(左后)
        /// </summary>
        public float FanPowerRear { get; set; }



        /// <summary>
        /// 启停前面的风扇
        /// </summary>
        /// <param name="isOpen">true:开启 false:关闭</param>
        /// <param name="PowerLeftFront">功率</param>
        public void FanPowerFrontOpen(bool isOpen,float powerValue=0)
        {
            if (isOpen)
            {
                S7CommunicationHelp.Instance.PLC_Write<FanDB_W, float>(x => x.FanPowerFront, powerValue);
            }
            else
            {
                S7CommunicationHelp.Instance.PLC_Write<FanDB_W, float>(x => x.FanPowerFront, 0);
            }

        }

        /// <summary>
        /// 启停后面的风扇
        /// </summary>
        /// <param name="isOpen">true:开启 false:关闭</param>
        /// <param name="PowerLeftFront">功率</param>
        public void FanPowerLeftRearOpen(bool isOpen, float powerValue = 0)
        {
            if (isOpen)
            {
                S7CommunicationHelp.Instance.PLC_Write<FanDB_W, float>(x => x.FanPowerFront, powerValue);
            }
            else
            {
                S7CommunicationHelp.Instance.PLC_Write<FanDB_W, float>(x => x.FanPowerFront, 0);
            }

        }
    }
}

  8、调用类

查看代码
 
using static S7Communication.S7Enums;

namespace S7Communication
{
    public class Progarm
    {
       public static void Main(string[] args)
        {
            PLC_CPUType _CPUType = PLC_CPUType.S71200;
            string ip = "192.168.1.32";
            short rack = 0;
            short slot=1;
            S7CommunicationHelp.Instance.PLCConnect(_CPUType,ip, rack,slot);

            if (S7CommunicationHelp.Instance.IsConnected)
            {
                //开启左前方风扇
                FanViewModel.Instance.FanPowerFrontOpen(true,30);

                //关闭左前方风扇
                FanViewModel.Instance.FanPowerFrontOpen(false);


            }
        }
    }

}

以上内容仅供参考

具体代码可以通过github下载:https://github.com/luoyekuangfeng/S7Communication.git