编辑
2025-02-05
C# 应用
00
请注意,本文编写于 90 天前,最后修改于 90 天前,其中某些信息可能已经过时。

目录

主要特点
注意事项
nuget 安装 S7netplus
主要功能
数据类型对应关系表
准备测试数据
连接PLC
Convert类
读取信息
写数据
批量读
批量写
读到一个类
IMQ读写
连续读取
注意事项

S7netplus 是一个开源的库,主要用于.NET环境中与西门子S7系列PLC进行通信。这个库允许开发者使用C#或其他.NET支持的编程语言来读写PLC的数据块、输入输出、标记等,非常适合于工业自动化领域的应用开发。

主要特点

  1. 易于使用:S7netplus 提供了简洁的API,使得开发者可以轻松地连接PLC,读取和写入数据。
  2. 开源:作为一个开源项目,S7netplus 允许开发者查看源代码,也欢迎社区贡献和改进。
  3. 跨平台:基于.NET平台,理论上支持所有.NET能运行的平台,包括Windows, Linux, 和 macOS。

使用S7-PLCSIM Advanced 3.0/4.0/5.0 仿真

image.png

如果启动许可证找不到,在服务中启动

image.png

注意事项

  • IP地址与子网掩码

image.png

  • 在TIA中IP修改与防真中的一样
  • 勾选Connection mechanisms中 Permit access with PUT/GET communication from remote partnet

image.png

  • 项目属性中修改 Protection 勾选Support simulation during block compilation

image.png

  • 确保Siemens PLCSIM Virtual Ethernet Adapter IP与防真IP在一个网段

image.png

  • 控制面板PG/PC

image.png

nuget 安装 S7netplus

S7netplus 是一个开源的 .NET 库,用于与西门子 S7 PLC(可编程逻辑控制器)进行通信。这个库主要用于读取和写入西门子 S7 系列 PLC 的数据块,支持多种数据类型的读写,如布尔型、整型、实数型等。S7netplus 通过以太网连接实现与 PLC 的通信,使用了西门子的 S7 通信协议。

主要功能

  1. 读取和写入数据: S7netplus 允许用户从 PLC 读取数据或向 PLC 写入数据。这包括了对 PLC 内部的 DB(数据块)、输入、输出、M(标志)内存区域的操作。
  2. 连接管理: 库提供了连接和断开与 PLC 的连接的功能,支持通过 IP 地址和端口连接到 PLC。
  3. 错误处理: S7netplus 包含了一套错误处理机制,可以识别和响应通信过程中的各种错误情况。
  4. 数据类型支持: 支持多种 PLC 数据类型的读写,包括但不限于布尔型、字节、字、双字、实数等。

image.png

数据类型对应关系表

S7-1500 数据类型S7netplus 数据类型C# 数据类型描述
BOOLBitbool布尔值,占用 1 位
BYTEBytebyte8位无符号整数
WORDWordushort16位无符号整数
DWORDDWorduint32位无符号整数
INTIntshort16位有符号整数
DINTDIntint32位有符号整数
REALRealfloat32位浮点数
SINTSIntsbyte8位有符号整数
USINTUSIntbyte8位无符号整数
UINTUIntushort16位无符号整数
UDINTUDIntuint32位无符号整数
LINTLIntlong64位有符号整数
ULINTULIntulong64位无符号整数
LREALLRealdouble64位浮点数
STRINGStringstring字符串类型
TIMETimeTimeSpan时间,通常以毫秒为单位
S5TIMES5TimeTimeSpanS5 时间格式
DATEDateDateTime日期
TIME_OF_DAYTimeOfDayDateTime一天中的时间
DATE_AND_TIMEDateTimeDateTime日期和时间

准备测试数据

image.png

连接PLC

image.png

C#
Plc plc; private async void btnConnect_Click(object sender, EventArgs e) { plc = new Plc(CpuType.S71500, "192.168.0.10", 0, 1); try { await plc.OpenAsync(); stsMain_lblStatus.Text = "连接成功"; } catch { stsMain_lblStatus.Text = "连接出错"; } }

Convert类

C#
using S7.Net.Types; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; namespace s7modbus01 { // // Summary: // Conversion methods to convert from Siemens numeric format to C# and back public static class Conversion { // // Summary: // Converts a binary string to Int32 value // // Parameters: // txt: public static int BinStringToInt32(this string txt) { int num = 0; for (int i = 0; i < txt.Length; i++) { num = (num << 1) | ((txt[i] == '1') ? 1 : 0); } return num; } // // Summary: // Converts a binary string to a byte. Can return null. // // Parameters: // txt: public static byte? BinStringToByte(this string txt) { if (txt.Length == 8) { return (byte)txt.BinStringToInt32(); } return null; } // // Summary: // Converts the value to a binary string // // Parameters: // value: public static string ValToBinString(this object value) { int num = 0; int num2 = 0; int num3 = 0; string text = ""; long num4 = 0L; try { if (value.GetType().Name.IndexOf("[]") < 0) { switch (value.GetType().Name) { case "Byte": num3 = 7; num4 = (byte)value; break; case "Int16": num3 = 15; num4 = (short)value; break; case "Int32": num3 = 31; num4 = (int)value; break; case "Int64": num3 = 63; num4 = (long)value; break; default: throw new Exception(); } for (num = num3; num >= 0; num += -1) { text = (((num4 & (long)Math.Pow(2.0, num)) <= 0) ? (text + "0") : (text + "1")); } } else { switch (value.GetType().Name) { case "Byte[]": { num3 = 7; byte[] array4 = (byte[])value; for (num2 = 0; num2 <= array4.Length - 1; num2++) { for (num = num3; num >= 0; num += -1) { text = (((array4[num2] & (byte)Math.Pow(2.0, num)) <= 0) ? (text + "0") : (text + "1")); } } break; } case "Int16[]": { num3 = 15; short[] array2 = (short[])value; for (num2 = 0; num2 <= array2.Length - 1; num2++) { for (num = num3; num >= 0; num += -1) { text = (((array2[num2] & (byte)Math.Pow(2.0, num)) <= 0) ? (text + "0") : (text + "1")); } } break; } case "Int32[]": { num3 = 31; int[] array3 = (int[])value; for (num2 = 0; num2 <= array3.Length - 1; num2++) { for (num = num3; num >= 0; num += -1) { text = (((array3[num2] & (byte)Math.Pow(2.0, num)) <= 0) ? (text + "0") : (text + "1")); } } break; } case "Int64[]": { num3 = 63; byte[] array = (byte[])value; for (num2 = 0; num2 <= array.Length - 1; num2++) { for (num = num3; num >= 0; num += -1) { text = (((array[num2] & (byte)Math.Pow(2.0, num)) <= 0) ? (text + "0") : (text + "1")); } } break; } default: throw new Exception(); } } return text; } catch { return ""; } } // // Summary: // Helper to get a bit value given a byte and the bit index. Example: DB1.DBX0.5 // -> var bytes = ReadBytes(DB1.DBW0); bool bit = bytes[0].SelectBit(5); // // Parameters: // data: // // bitPosition: public static bool SelectBit(this byte data, int bitPosition) { int num = 1 << bitPosition; return (data & num) != 0; } // // Summary: // Converts from ushort value to short value; it's used to retrieve negative values // from words // // Parameters: // input: public static short ConvertToShort(this ushort input) { return short.Parse(input.ToString("X"), NumberStyles.HexNumber); } // // Summary: // Converts from short value to ushort value; it's used to pass negative values // to DWs // // Parameters: // input: public static ushort ConvertToUshort(this short input) { return ushort.Parse(input.ToString("X"), NumberStyles.HexNumber); } // // Summary: // Converts from UInt32 value to Int32 value; it's used to retrieve negative values // from DBDs // // Parameters: // input: public static int ConvertToInt(this uint input) { return int.Parse(input.ToString("X"), NumberStyles.HexNumber); } // // Summary: // Converts from Int32 value to UInt32 value; it's used to pass negative values // to DBDs // // Parameters: // input: public static uint ConvertToUInt(this int input) { return uint.Parse(input.ToString("X"), NumberStyles.HexNumber); } // // Summary: // Converts from float to DWord (DBD) // // Parameters: // input: public static uint ConvertToUInt(this float input) { return DWord.FromByteArray(Real.ToByteArray(input)); } // // Summary: // Converts from DWord (DBD) to float // // Parameters: // input: public static float ConvertToFloat(this uint input) { return Real.FromByteArray(DWord.ToByteArray(input)); } /// <summary> /// 转换日期 /// </summary> /// <param name="bytes"></param> /// <returns></returns> /// <exception cref="ArgumentException"></exception> public static System.DateTime ConvertToDateTime(this byte[] bytes) { if (bytes.Length != 8) throw new ArgumentException("Invalid length of bytes for DATE_AND_TIME."); // 第0字节表示从1990年开始的年份偏移 int year =2000+ BcdToDecimal(bytes[0]); int month = BcdToDecimal(bytes[1]); int day = BcdToDecimal(bytes[2]); int hour = BcdToDecimal(bytes[3]); int minute = BcdToDecimal(bytes[4]); int second = BcdToDecimal(bytes[5]); // 字节6和7合并表示毫秒 int millisecond = (BcdToDecimal(bytes[6])*10 + BcdToDecimal(bytes[7])/10); return new System.DateTime(year, month, day, hour, minute, second, millisecond); } private static int BcdToDecimal( byte bcd) { return ((bcd >> 4) * 10) + bcd % 16; } //将前两位计算出来拼接 public static byte[] SToByteArray(this string value) { byte[] data = System.Text.Encoding.Default.GetBytes(value); byte[] head = new byte[2]; head[0] = Convert.ToByte(254); head[1] = Convert.ToByte(value.Length); data = head.Concat(data).ToArray(); return data; } //将前两位计算出来拼接 public static byte[] WSToByteArray(this string value) { byte[] data = System.Text.Encoding.BigEndianUnicode.GetBytes(value); byte[] head = BitConverter.GetBytes((short)508); byte[] length = BitConverter.GetBytes((short)value.Length); Array.Reverse(head); Array.Reverse(length); head = head.Concat(length).ToArray(); data = head.Concat(data).ToArray(); byte[] d = new byte[512]; Array.Copy(data, d, data.Length); return d; } } }

读取信息

C#
private async void btnRead_Click(object sender, EventArgs e) { // 从数据块1的位0读取布尔值 var b1 = await plc.ReadAsync("DB1.DBX0.0"); // 从数据块1的字2读取无符号短整型(16位) var us1 = await plc.ReadAsync("DB1.DBW2.0"); // 从数据块1的双字4读取无符号整型(32位) var u1 = await plc.ReadAsync("DB1.DBD4.0"); // 从数据块1的双字8读取浮点数,并转换为float类型 var r1 = ((uint)plc.Read("DB1.DBD8.0")).ConvertToFloat(); // 从数据块1的双字12读取无符号整型(32位) var t1 = await plc.ReadAsync("DB1.DBD12.0"); // 异步从数据块1的字节16开始读取254个字节 var s1 = await plc.ReadBytesAsync(DataType.DataBlock, 1, 16, 254); // 异步从数据块1的字节272开始读取508个字节 var s2 = await plc.ReadBytesAsync(DataType.DataBlock, 1, 272, 508); // 从数据块1的字节784开始同步读取8个字节(用于日期和时间) var d1 = plc.ReadBytes(DataType.DataBlock, 1, 784, 8); // 将读取的字节转换为DateTime对象 var dateTime = d1.ConvertToDateTime(); // 将读取的值显示在界面上 txt1.Text = b1.ToString(); // 显示布尔值 txt2.Text = us1.ToString(); // 显示无符号短整型 txt3.Text = u1.ToString(); // 显示无符号整型 txt4.Text = r1.ToString(); // 显示浮点数 txt5.Text = t1.ToString(); // 显示无符号整型 txt6.Text = System.Text.Encoding.Default.GetString(s1.Skip(2).ToArray()); // 显示字符串(跳过前两个字节) txt7.Text = System.Text.Encoding.BigEndianUnicode.GetString(s2.Skip(4).ToArray()); // 显示字符串(跳过前四个字节并使用大端编码) txt8.Text = dateTime.ToString(); // 显示日期时间 }

在S7-1500中,一个String类型的变量占用256个字节,但是第一个字节是总字符数,第二个字节是当前字符数,所以真正的字符数据是从第三个字节开始的,共254个字节。

同理,WString类型其实就是双字节的Sring,也就是说一个字符占用两个字节,所以一个WString类型的变量占用512个字节,第一、二个字节是总字符数,第三、四个字节是当前字符数,真正的字符数据是从第五个字节开始的,共508个字节。

写数据

C#
private async void btnWrite_Click(object sender, EventArgs e) { await plc.WriteAsync(DataType.DataBlock, 1, 0, txt1.Text == "true" ? true : false); await plc.WriteAsync(DataType.DataBlock, 1, 2, ushort.Parse(txt2.Text)); await plc.WriteAsync(DataType.DataBlock, 1, 4, uint.Parse(txt3.Text)); await plc.WriteAsync(DataType.DataBlock, 1, 8, float.Parse(txt4.Text)); await plc.WriteAsync(DataType.DataBlock, 1, 12, uint.Parse(txt5.Text)); await plc.WriteAsync(DataType.DataBlock, 1, 16, txt6.Text.SToByteArray()); await plc.WriteAsync(DataType.DataBlock, 1, 272, txt7.Text.WSToByteArray()); await plc.WriteAsync(DataType.DataBlock, 1, 784, System.DateTime.Parse(txt8.Text)); }

批量读

C#
private async void btnBatchRead_Click(object sender, EventArgs e) { List<DataItem> datas = new List<DataItem>(); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.Bit, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 0, Value = new object() }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.Int, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 2, Value = new object() }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.DInt, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 4, Value = new object() }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.Real, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 8, Value = new object() }); //time对应双字 datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.DWord, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 12, Value = new object() }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.S7String, DB = 1, BitAdr = 0, Count = 254, StartByteAdr = 16, Value = new object() }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.S7WString, DB = 1, BitAdr = 0, Count = 254, StartByteAdr = 272, Value = new object() }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.DateTime, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 784, Value = new object() }); await plc.ReadMultipleVarsAsync(datas); txt1.Text = datas[0].Value.ToString(); txt2.Text = datas[1].Value.ToString(); txt3.Text = datas[2].Value.ToString(); txt4.Text = datas[3].Value.ToString(); txt5.Text = datas[4].Value.ToString(); txt6.Text = datas[5].Value.ToString(); txt7.Text = datas[6].Value.ToString(); txt8.Text = ((System.DateTime)datas[7].Value).ToString("yyyy-MM-dd HH:mm:ss"); }

批量写

C#
private async void btnBatchWrite_Click(object sender, EventArgs e) { List<DataItem> datas = new List<DataItem>(); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.Bit, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 0, Value = txt1.Text.ToLower() == "true" ? true : false }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.Int, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 2, Value = ushort.Parse(txt2.Text) }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.DInt, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 4, Value = int.Parse(txt3.Text) }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.Real, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 8, Value = float.Parse(txt4.Text) }); //time对应双字 datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.DWord, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 12, Value = int.Parse(txt5.Text) }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.S7String, DB = 1, BitAdr = 0, Count = 254, StartByteAdr = 16, Value = txt6.Text }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.S7WString, DB = 1, BitAdr = 0, Count = 254, StartByteAdr = 272, Value = txt7.Text }); datas.Add(new DataItem { DataType = DataType.DataBlock, VarType = VarType.DateTime, DB = 1, BitAdr = 0, Count = 1, StartByteAdr = 784, Value = System.DateTime.Parse(txt8.Text) }); await plc.WriteAsync(datas.ToArray()); }

image.png

读到一个类

C#
public class CData { public bool b1 { get; set; } public ushort us1 { get; set; } public uint u1 { get; set; } public float r1 { get; set; } public int t1 { get; set; } [S7StringAttribute(S7StringType.S7String, 254)] public string s1 { get; set; } [S7StringAttribute(S7StringType.S7WString, 254)] public string s2 { get; set; } public byte[] d1 { get; set; } = new byte[8]; // 存储原始字节数据 }
C#
private void btnReadClass_Click(object sender, EventArgs e) { CData cData= new CData(); plc.ReadClass(cData, 1, 0); txt1.Text = cData.b1.ToString(); txt2.Text = cData.us1.ToString(); txt3.Text = cData.u1.ToString(); txt4.Text = cData.r1.ToString(); txt5.Text = cData.t1.ToString(); txt6.Text = cData.s1.ToString(); txt7.Text = cData.s2.ToString(); txt8.Text = cData.d1.ConvertToDateTime().ToString("yyyy-MM-dd HH:mm:ss"); }

IMQ读写

C#
private void btnReadIMQ_Click(object sender, EventArgs e) { var db1 = (bool)plc.Read("I0.0"); var db2 = (bool)plc.Read("Q0.1"); var db3 = (ushort)plc.Read("MW1"); var db4 = plc.Read("M0.2"); } private void btnWriteIMQ_Click(object sender, EventArgs e) { plc.Write("I0.0", true); plc.Write("Q0.0", true); plc.Write("MW1",(ushort)2); plc.Write("M0.2", true); }

连续读取

C#
private void btnConRead_Click(object sender, EventArgs e) { // 从数据块1的偏移量0开始读取792字节的数据(784字节数据+8字节日期时间) byte[] bs = plc.ReadBytes(DataType.DataBlock, 1, 0, 784+8); // 读取并转换布尔值,位于第一个字节的第0位 var b1 = S7.Net.Types.Boolean.GetValue(bs.Take(1).ToArray()[0], 0); // 跳过1个字节(通常是填充或未使用的字节),然后读取2字节并转换为无符号短整型 var us1 = S7.Net.Types.Word.FromByteArray(bs.Skip(2).Take(2).ToArray()); // 跳过4字节,读取4字节并转换为无符号整型 var u1 = S7.Net.Types.DWord.FromByteArray(bs.Skip(4).Take(4).ToArray()); // 跳过8字节,读取4字节并转换为浮点数 var r1 = S7.Net.Types.Real.FromByteArray(bs.Skip(8).Take(4).ToArray()); // 跳过12字节,读取4字节并转换为整型 var t1 = S7.Net.Types.DInt.FromByteArray(bs.Skip(12).Take(4).ToArray()); // 跳过16字节,读取256字节并转换为标准字符串 var s1 = S7.Net.Types.S7String.FromByteArray(bs.Skip(16).Take(256).ToArray()); // 跳过272字节,读取512字节并转换为宽字符字符串 var s2 = S7.Net.Types.S7WString.FromByteArray(bs.Skip(272).Take(512).ToArray()); // 跳过784字节,读取接下来的8字节数据作为日期时间 var d = bs.Skip(784).Take(8).ToArray(); var d1 = S7.Net.Types.DateTime.FromByteArray(d); // 将读取的数据显示在界面上 txt1.Text = b1.ToString(); txt2.Text = us1.ToString(); txt3.Text = u1.ToString(); txt4.Text = r1.ToString(); txt5.Text = t1.ToString(); txt6.Text = s1.ToString(); txt7.Text = s2.ToString(); txt8.Text = d1.ToString("yyyy-MM-dd HH:mm:ss"); }

注意事项

  1. 数据对齐和偏移:确保您读取的字节偏移和数量与 PLC 中的数据块配置一致。错误的偏移可能导致错误的数据解析。
  2. 数据类型转换:使用 S7.Net 类型方法(如 Word, DWord, Real 等)确保数据按照正确的格式转换。
  3. 异常处理:在实际应用中,您可能需要添加异常处理逻辑来处理通信错误或数据解析错误。

image.png

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!