S7协议(通用写法)
S7 通信协议是西门子公司推出的一种工业通信协议,用于在工业自动化领域中实现设备之间的通信。它是一种基于以太网的协议,支持多种通信方式,如 TCP/IP、UDP/IP、ISO-on-TCP 等。
主要用于西门子 S7 系列 PLC(可编程逻辑控制器)之间的通信,也可以用于与其他设备之间的通信,如人机界面(HMI)、变频器、传感器等。通过 S7 通信协议,可以实现数据的读取、写入、传输和处理等功能,从而实现设备之间的互联互通。具有高效、可靠、安全等特点,广泛应用于工业自动化领域。同时,西门子公司还提供了丰富的工具和软件,方便用户进行 S7 通信协议的开发和应用。
使用西门子提供的工具和软件进行 S7 通信协议的开发和应用,一般需要以下步骤:
- 安装西门子提供的工具和软件:首先需要安装西门子提供的相关工具和软件,如 TIA Portal(博途)、STEP 7 等。这些工具和软件可以帮助用户进行 S7 通信协议的配置、编程和调试。
- 配置通信模块:在进行 S7 通信协议的开发和应用之前,需要先配置通信模块,如以太网模块、PROFIBUS 模块等。在配置通信模块时,需要设置模块的参数,如 IP 地址、子网掩码、网关等。
- 编写通信程序:在配置好通信模块后,可以使用西门子提供的编程语言,如 LAD(梯形图)、FBD(功能块图)、STL(语句表)等,编写通信程序。在编写通信程序时,需要使用 S7 通信协议提供的指令和功能块,如 READ、WRITE、TRANSMIT、RECEIVE 等。
- 调试通信程序:在编写好通信程序后,可以使用西门子提供的调试工具,如 PLCSIM(仿真软件)等,对通信程序进行调试。在调试过程中,可以查看通信数据的传输情况,以及检查程序是否存在错误。
- 应用通信程序:在调试通过后,可以将通信程序下载到实际的设备中,进行实际的通信应用。在应用通信程序时,需要注意设备之间的连接方式、数据格式等,以确保通信的稳定性和可靠性。
开发语言使用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/Communication.git