采用S7.NET库开发S7-1200上位机软件。软件采用VS2022。具体开发流程如下.
1. S7.NET介绍:
How to download s7.Net
The official repository is on GitHub (https://github.com/killnine/s7netplus), you can also download the library directly from NuGet (https://www.nuget.org/packages/S7netplus/).
What is S7.Net
S7.Net is a plc driver that works only with Siemens PLC and only with Ethernet connection. This means that your plc must have a Profinet CPU or a profinet external card (CPxxx card).
S7.Net is written entirely in C#, so you can debug it easily without having to go through native dlls.
Supported PLC
S7.Net is compatible with S7-200, S7-300, S7-400, S7-1200, S7-1500.
2. VS创建C#窗体项目:
(1)创建项目:
注意:创建过程中,选取自己需要的框架,这里选取.NET 8.0。
(2)添加S7.NET的NuGet程序包。
右键->管理NuGet程序包
浏览S7net, 选择S7netplus,版本选择最新的0.20.0,点击安装。
等待安装结束。
(3)编辑Form1界面,添加需要的控件,如显示窗口TextBox,Label,Button等。
根据自己需求设计好Form1,
(4)编写代码
program.cs //一般不需要修改,可以创建全局类、全局变量等。
Form1.cs //主要集中在这里面。
Form1.Designer.cs //一般不需要修改。
FromS7.cs、NumberFromString.cs //包含在Types文件夹中,是我自己创建的数据处理的方法、类等的命名空间。
以下是各个源码。
Program.cs
//Program.cs
namespace S7ComTest
{
public static class Global
{
public static int Result = 0;
public static bool Connecting = false;
}
public class Db1Read : ICloneable
{
public bool Bool1 { get; set; }
public bool Bool2 { get; set; }
public bool Bool3 { get; set; }
public bool Bool4 { get; set; }
public byte Byte1 { get; set; }
public byte Byte2 { get; set; }
public short Int { get; set; }
public int Dint { get; set; }
public float Real { get; set; }
public string String { get; set; } = string.Empty;
public ushort Word { get; set; }
public object Clone()
{
return new Db1Read
{
Bool1 = this.Bool1,
Bool2 = this.Bool2,
Bool3 = this.Bool3,
Bool4 = this.Bool4,
Byte1 = this.Byte1,
Byte2 = this.Byte2,
Int = this.Int,
Dint = this.Dint,
Real = this.Real,
String = this.String,
Word = this.Word
};
}
}
public class Db1Write : ICloneable
{
public bool Bool1 { get; set; }
public bool Bool2 { get; set; }
public bool Bool3 { get; set; }
public bool Bool4 { get; set; }
public byte Byte1 { get; set; }
public byte Byte2 { get; set; }
public short Int { get; set; }
public int Dint { get; set; }
public float Real { get; set; }
public string String { get; set; } = string.Empty;
public ushort Word { get; set; }
public object Clone()
{
return new Db1Write
{
Bool1 = this.Bool1,
Bool2 = this.Bool2,
Bool3 = this.Bool3,
Bool4 = this.Bool4,
Byte1 = this.Byte1,
Byte2 = this.Byte2,
Int = this.Int,
Dint = this.Dint,
Real = this.Real,
String = this.String,
Word = this.Word
};
}
}
public partial class Form1 : Form
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}
}
}
Form1.cs
//Form1.cs
using S7.Net;//S7通讯,需要开启西门子PUT/GET
using S7.Net.Types;//数据转换函数包含其中
using S7ComTest.Types;
using System.Diagnostics;
namespace S7ComTest
{
public partial class Form1 : Form
{
private Plc plc;//
private System.Windows.Forms.Timer refreshTimer;//定义一个新的刷新计时器
private Db1Read db1Read = new Db1Read();
private Db1Write lastDb1Write = new Db1Write(); // 缓存上次写入区值
private Db1Read lastDb1Read = new Db1Read(); // 缓存上次读取区值
private Db1Write db1Write = new Db1Write();
//private Db1Write lastWriteValues = new Db1Write();
private bool isFirstRead = true;
private bool _isPLCUpdate = false;
private CancellationTokenSource? debounceTokenSource;
private Dictionary<string, string> pendingWrites = new Dictionary<string, string>();//定义字典,包含文本框名字和实际string型值
//private Dictionary<string, bool> userModfiedFlags = new Dictionary<string, bool>();//定义字典,包含文本框名字,和是否被修改标志
private Dictionary<string, bool> userModfiedFlags = new Dictionary<string, bool>();
private Dictionary<string, bool> isEditingFlags = new Dictionary<string, bool>();
private List<string> readOnlyTextBoxNames = new List<string>();//只读取文本框名称列表
private List<string> readWriteTextBoxNames = new List<string>();//读写区文本框名称列表
private readonly object _lockObject = new object(); // 用于线程同步的锁对象
private Dictionary<string, object> lastPLCValues = new Dictionary<string, object>()
{
{ nameof(textBox14), false },
{ nameof(textBox15), false },
{ nameof(textBox16), false },
{ nameof(textBox17), false },
{ nameof(textBox18), (byte)0 },
{ nameof(textBox19), (byte)0 },
{ nameof(textBox20), (short)0 },
{ nameof(textBox21), 0 },
{ nameof(textBox22), 0f },
{ nameof(textBox23), string.Empty },
{ nameof(textBox24), (ushort)0 }
};
public Form1()
{
//从这里运行
InitializeComponent();
plc = new Plc(CpuType.S71200, "192.168.10.20", 0, 1);//创建PLC连接接口
InitializeTextBoxLists();//初始化文本框列表
InitializeUserModfiedFlags();//初始化用户修改标示
InitializeEditingFlags();
SetupReadWriteTextBoxEvents();//为读写文本框添加事件处理
refreshTimer = new System.Windows.Forms.Timer { Interval = 1000 };//创建刷新计时器,设定时间为1000ms
refreshTimer.Tick += RefreshTimer_Tick;
}
// 修改更新方法
private void UpdateLastPLCValues()
{
lastPLCValues[nameof(textBox14)] = db1Write.Bool1;
lastPLCValues[nameof(textBox15)] = db1Write.Bool2;
lastPLCValues[nameof(textBox16)] = db1Write.Bool3;
lastPLCValues[nameof(textBox17)] = db1Write.Bool4;
lastPLCValues[nameof(textBox18)] = db1Write.Byte1;
lastPLCValues[nameof(textBox19)] = db1Write.Byte2;
lastPLCValues[nameof(textBox20)] = db1Write.Int;
lastPLCValues[nameof(textBox21)] = db1Write.Dint;
lastPLCValues[nameof(textBox22)] = db1Write.Real;
lastPLCValues[nameof(textBox23)] = db1Write.String;
lastPLCValues[nameof(textBox24)] = db1Write.Word;
}
private void InitializeTextBoxLists()
{
//设置只读取文本框
readOnlyTextBoxNames.AddRange(new[] {
nameof(textBox1), nameof(textBox2), nameof(textBox3), nameof(textBox4),
nameof(textBox5), nameof(textBox6), nameof(textBox7), nameof(textBox8),
nameof(textBox9), nameof(textBox10), nameof(textBox11), nameof(textBox12),
nameof(textBox13)
});
//设置读写区文本框
readWriteTextBoxNames.AddRange(new[] {
nameof(textBox14), nameof(textBox15), nameof(textBox16), nameof(textBox17),
nameof(textBox18), nameof(textBox19), nameof(textBox20), nameof(textBox21),
nameof(textBox22), nameof(textBox23), nameof(textBox24)
});
}
private void InitializeUserModfiedFlags()
{
//为所有读写区域文本框初始化用户修改标志
foreach (var textBoxName in readWriteTextBoxNames)
{
userModfiedFlags[textBoxName] = false;
}
}
private void InitializeEditingFlags()
{
foreach(var textBoxName in readWriteTextBoxNames)
{
isEditingFlags[textBoxName] = false;
}
}
private void SetupReadWriteTextBoxEvents()
{
//为读写区的每个TextBox添加TextChanged和LostFocus事件处理
foreach (var textBoxName in readWriteTextBoxNames)
{
if (Controls.Find(textBoxName, true).FirstOrDefault() is TextBox textBox)
{
textBox.TextChanged += WriteTextBox_TextChanged;
textBox.LostFocus += WriteTextBox_LostFocus;
textBox.GotFocus += WriteTextBox_GotFocus;
}
}
}
private void WriteTextBox_TextChanged(object? sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
// 如果是PLC主动更新导致的文本变化,不标记为用户修改
if (_isPLCUpdate)
return;
// 记录用户修改的值的标志位
userModfiedFlags[textBox.Name] = true;
// 使用防抖机制避免频繁触发
Debounce(() => {
pendingWrites[textBox.Name] = textBox.Text;
ApplyPendingWrites();
CheckAndWriteData();
}, 500);
textBox.BackColor = Color.LightYellow;
}
private void WriteTextBox_LostFocus(object? sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
string textBoxName = textBox.Name;
isEditingFlags[textBoxName] = false;
// 安全获取当前值
object? currentValue = GetValueFromText(textBoxName, textBox.Text);
if (currentValue == null) return; // 转换失败
// 获取最后已知PLC值
object lastPLCValue = lastPLCValues[textBoxName];
// 比较值是否变化
if (!ValuesEqual(currentValue, lastPLCValue))
{
// 值有变化,添加到待写入
pendingWrites[textBoxName] = textBox.Text;
ApplyPendingWrites();
CheckAndWriteData();
}
else
{
// 值未变化,重置修改标志
userModfiedFlags[textBoxName] = false;
textBox.BackColor = SystemColors.Window; // 恢复背景色
}
}
private void WriteTextBox_GotFocus(object? sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
isEditingFlags[textBox.Name] = true;
userModfiedFlags[textBox.Name] = true;
}
private bool ValuesEqual(object value1, object value2)
{
// 处理浮点数特殊比较
if (value1 is float f1 && value2 is float f2)
{
return Math.Abs(f1 - f2) < 0.0001;
}
// 处理字符串比较
if (value1 is string s1 && value2 is string s2)
{
return string.Equals(s1, s2, StringComparison.Ordinal);
}
// 默认比较
return Equals(value1, value2);
}
private void Debounce(Action action, int delayMs)
{
//取消之前的延时操作
debounceTokenSource?.Cancel();
debounceTokenSource = new CancellationTokenSource();
//启用新的延时操作
Task.Delay(delayMs, debounceTokenSource.Token).ContinueWith(t =>
{
if (!t.IsCanceled)
{
Invoke(action);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void button1_Click(object sender, EventArgs e)
{
try
{
if (plc.IsConnected)
{
// 断开连接
refreshTimer.Stop();
plc.Close();
Global.Connecting = false;
// 重置所有状态
ResetAllStates();
MessageBox.Show("已断开与PLC的连接");
}
else
{
// 建立连接
plc.Open();
Global.Connecting = plc.IsConnected;
if (plc.IsConnected)
{
// 重置所有状态
ResetAllStates();
refreshTimer.Start();
MessageBox.Show("已经成功连接PLC,开始自动刷新");
}
else
{
MessageBox.Show("连接失败,请检查PLC地址和网络");
}
}
UpdateReadOnlyUI();
UpdateReadWriteUI();
}
catch (Exception ex)
{
MessageBox.Show($"操作出错:{ex.Message}");
Global.Connecting = false;
refreshTimer.Stop();
ResetAllStates();
}
}
private void ResetAllStates()
{
// 重置标志
isFirstRead = true;
// 重置用户修改标志
foreach (var key in userModfiedFlags.Keys.ToList())
{
userModfiedFlags[key] = false;
}
// 重置编辑状态标志
foreach (var key in isEditingFlags.Keys.ToList())
{
isEditingFlags[key] = false;
}
// 清空待写入队列
pendingWrites.Clear();
debounceTokenSource?.Cancel();
// 重置背景色
foreach (var textBoxName in readWriteTextBoxNames)
{
if (Controls.Find(textBoxName, true).FirstOrDefault() is TextBox textBox)
{
textBox.BackColor = SystemColors.Window;
}
}
// 重置缓存
lastPLCValues[nameof(textBox14)] = false;
lastPLCValues[nameof(textBox15)] = false;
lastPLCValues[nameof(textBox16)] = false;
lastPLCValues[nameof(textBox17)] = false;
lastPLCValues[nameof(textBox18)] = (byte)0;
lastPLCValues[nameof(textBox19)] = (byte)0;
lastPLCValues[nameof(textBox20)] = (short)0;
lastPLCValues[nameof(textBox21)] = 0;
lastPLCValues[nameof(textBox22)] = 0f;
lastPLCValues[nameof(textBox23)] = string.Empty;
lastPLCValues[nameof(textBox24)] = (ushort)0;
// 清空数据对象
db1Read = new Db1Read();
db1Write = new Db1Write();
lastDb1Write = new Db1Write();
}
private async void RefreshTimer_Tick(object? sender, EventArgs e)
{
if (!plc.IsConnected)
{
refreshTimer.Stop();
Global.Connecting = false;
// 重置状态
ResetAllStates();
ClearDataDisplay();
UpdateReadOnlyUI();
UpdateReadWriteUI();
return;
}
try
{
Db1Read oldDb1Read = (Db1Read)db1Read.Clone();
Db1Write oldDb1Write = (Db1Write)db1Write.Clone();
await ReadFromPLCAsync();
await ReadDbWriteFromPLCAsync();
if (isFirstRead)
{
lastDb1Write = (Db1Write)db1Write.Clone();
UpdateLastPLCValues();
UpdateReadOnlyUI();
UpdateReadWriteUI();
isFirstRead = false;
}
else
{
if (IsDb1ReadChanged(oldDb1Read))
{
UpdateReadOnlyUI();
}
if (IsDb1WriteChanged(oldDb1Write))
{
UpdateReadWriteUI();
lastDb1Write = (Db1Write)db1Write.Clone();
UpdateLastPLCValues();
}
}
}
catch (PlcException ex)
{
//返回PLC读取错误,显示弹窗
MessageBox.Show($"PLC读取错误:{ex.Message}");
refreshTimer.Stop();
Global.Connecting = false;
//清空所有数据显示
ClearDataDisplay();
UpdateReadOnlyUI();
UpdateReadWriteUI();
}
catch (Exception ex)
{
MessageBox.Show($"刷新出错:{ex.Message}");
refreshTimer.Stop();
}
}
private bool HasUserModifiedValue()
{
//检查是否有用户修改的值
return userModfiedFlags.ContainsValue(true);
}
private async Task ReadFromPLCAsync()
{
await Task.Run(() =>
{
byte[] buffer = new byte[272];
//读取PLC数据, 以字节形式批量读取
plc.ReadBytes(buffer: buffer.AsSpan(), dataType: DataType.DataBlock, db: 1, startByteAdr: 0);
//解析数据
db1Read.Bool1 = Bit.FromByte(buffer[0], 0);
db1Read.Bool2 = Bit.FromByte(buffer[0], 1);
db1Read.Bool3 = Bit.FromByte(buffer[0], 2);
db1Read.Bool4 = Bit.FromByte(buffer[0], 3);
db1Read.Byte1 = buffer[1];
db1Read.Byte2 = buffer[2];
db1Read.Int = Int.FromByteArray(buffer.AsSpan(4, 2).ToArray());
db1Read.Dint = DInt.FromByteArray(buffer.AsSpan(6, 4).ToArray());
db1Read.Real = Real.FromByteArray(buffer.AsSpan(10, 4).ToArray());
db1Read.String = S7String.FromByteArray(buffer.AsSpan(14, 256).ToArray());
db1Read.Word = Word.FromByteArray(buffer.AsSpan(270, 2).ToArray());
});
}
private async Task ReadDbWriteFromPLCAsync()
{
await Task.Run(() =>
{
byte[] buffer = new byte[272];
//读取PLC数据, 以字节形式批量读取
plc.ReadBytes(buffer: buffer.AsSpan(), dataType: DataType.DataBlock, db: 1, startByteAdr: 300);
//解析数据
db1Write.Bool1 = Bit.FromByte(buffer[0], 0);
db1Write.Bool2 = Bit.FromByte(buffer[0], 1);
db1Write.Bool3 = Bit.FromByte(buffer[0], 2);
db1Write.Bool4 = Bit.FromByte(buffer[0], 3);
db1Write.Byte1 = buffer[1];
db1Write.Byte2 = buffer[2];
db1Write.Int = Int.FromByteArray(buffer.AsSpan(4, 2).ToArray());
db1Write.Dint = DInt.FromByteArray(buffer.AsSpan(6, 4).ToArray());
db1Write.Real = Real.FromByteArray(buffer.AsSpan(10, 4).ToArray());
db1Write.String = S7String.FromByteArray(buffer.AsSpan(14, 256).ToArray());
db1Write.Word = Word.FromByteArray(buffer.AsSpan(270, 2).ToArray());
});
}
private async Task WriteToPLCAsync()
{
await Task.Run(() =>
{
byte[] buffer = new byte[272];
//填充数据
FromS7.Bit.ToByteArray(db1Write.Bool1, buffer, 0, 0);
FromS7.Bit.ToByteArray(db1Write.Bool2, buffer, 0, 1);
FromS7.Bit.ToByteArray(db1Write.Bool3, buffer, 0, 2);
FromS7.Bit.ToByteArray(db1Write.Bool4, buffer, 0, 3);
buffer[1] = db1Write.Byte1;
buffer[2] = db1Write.Byte2;
FromS7.Int.ToByteArray(db1Write.Int, buffer.AsSpan(4));
FromS7.Dint.ToByteArray(db1Write.Dint, buffer.AsSpan(6));
FromS7.Real.ToByteArray(db1Write.Real, buffer.AsSpan(10));
FromS7.S7String.ToByteArray(db1Write.String, buffer.AsSpan(), 14);
FromS7.Word.ToByteArray(db1Write.Word, buffer.AsSpan(270));
//执行PLC写入
plc.WriteBytes(DataType.DataBlock, 1, 300, buffer.AsSpan());
});
}
private void ApplyPendingWrites()
{
//将待写入的值应用到UI和DBWrite
foreach (var entry in pendingWrites)
{
//if (Controls.Find(entry.Key, true).FirstOrDefault() is TextBox textBox)
//{
// textBox.Text = entry.Value;
//根据TextBox名称更新对应的Db1Write属性
switch (entry.Key)
{
case nameof(textBox14):
db1Write.Bool1 = numberFromString.ParseBool(entry.Value);
break;
case nameof(textBox15):
db1Write.Bool2 = numberFromString.ParseBool(entry.Value);
break;
case nameof(textBox16):
db1Write.Bool3 = numberFromString.ParseBool(entry.Value);
break;
case nameof(textBox17):
db1Write.Bool4 = numberFromString.ParseBool(entry.Value);
break;
case nameof(textBox18):
db1Write.Byte1 = numberFromString.ParseByte(entry.Value);
break;
case nameof(textBox19):
db1Write.Byte2 = numberFromString.ParseByte(entry.Value);
break;
case nameof(textBox20):
db1Write.Int = numberFromString.ParseInt(entry.Value);
break;
case nameof(textBox21):
db1Write.Dint = numberFromString.ParseDint(entry.Value);
break;
case nameof(textBox22):
db1Write.Real = numberFromString.ParseReal(entry.Value);
break;
case nameof(textBox23):
db1Write.String = entry.Value;
break;
case nameof(textBox24):
db1Write.Word = numberFromString.ParseWord(entry.Value);
break;
default:
break;
}
//}
}
pendingWrites.Clear();
}
private async void CheckAndWriteData()
{
if (!plc.IsConnected) return;
try
{
await WriteToPLCAsync();
UpdateStatusMessage("写入成功");
// 写入成功后,只重置当前控件的修改标志
foreach (var entry in pendingWrites)
{
userModfiedFlags[entry.Key] = false;
}
// 更新PLC值缓存
UpdateLastPLCValues();
}
catch (Exception ex)
{
UpdateStatusMessage($"写入失败:{ex.Message}");
}
}
private object? GetValueFromText(string textBoxName, string text)
{
switch (textBoxName)
{
case nameof(textBox14): return numberFromString.ParseBool(text);
case nameof(textBox15): return numberFromString.ParseBool(text);
case nameof(textBox16): return numberFromString.ParseBool(text);
case nameof(textBox17): return numberFromString.ParseBool(text);
case nameof(textBox18): return numberFromString.ParseByte(text);
case nameof(textBox19): return numberFromString.ParseByte(text);
case nameof(textBox20): return numberFromString.ParseInt(text);
case nameof(textBox21): return numberFromString.ParseDint(text);
case nameof(textBox22): return numberFromString.ParseReal(text);
case nameof(textBox23): return text;
case nameof(textBox24): return numberFromString.ParseWord(text);
default: return null;
}
}
private bool IsDb1ReadChanged(Db1Read lastRead)
{
return db1Read.Bool1 != lastRead.Bool1 ||
db1Read.Bool2 != lastRead.Bool2 ||
db1Read.Bool3 != lastRead.Bool3 ||
db1Read.Bool4 != lastRead.Bool4 ||
db1Read.Byte1 != lastRead.Byte1 ||
db1Read.Byte2 != lastRead.Byte2 ||
db1Read.Int != lastRead.Int ||
db1Read.Dint != lastRead.Dint ||
db1Read.Real != lastRead.Real ||
db1Read.String != lastRead.String ||
db1Read.Word != lastRead.Word;
}
private bool IsDb1WriteChanged(Db1Write oldValues)
{
return oldValues.Bool1 != db1Write.Bool1 ||
oldValues.Bool2 != db1Write.Bool2 ||
oldValues.Bool3 != db1Write.Bool3 ||
oldValues.Bool4 != db1Write.Bool4 ||
oldValues.Byte1 != db1Write.Byte1 ||
oldValues.Byte2 != db1Write.Byte2 ||
oldValues.Int != db1Write.Int ||
oldValues.Dint != db1Write.Dint ||
Math.Abs(oldValues.Real - db1Write.Real) > 0.001 || // 浮点数比较容差
oldValues.String != db1Write.String ||
oldValues.Word != db1Write.Word;
}
private void UpdateStatusMessage(string message)
{
richTextBox1.AppendText($"[{System.DateTime.Now:HH:mm:ss}] {message}\n");
richTextBox1.ScrollToCaret();
}
private void UpdateReadOnlyUI()
{
//更新只读区文本框
textBox3.Text = db1Read.Bool1.ToString();
textBox4.Text = db1Read.Bool2.ToString();
textBox5.Text = db1Read.Bool3.ToString();
textBox6.Text = db1Read.Bool4.ToString();
textBox7.Text = "16#" + db1Read.Byte1.ToString("x2");
textBox8.Text = "16#" + db1Read.Byte2.ToString("x2");
textBox9.Text = db1Read.Int.ToString();
textBox10.Text = db1Read.Dint.ToString();
textBox11.Text = db1Read.Real.ToString();
textBox12.Text = db1Read.String.ToString();
textBox13.Text = "16#" + db1Read.Word.ToString("x2");
//更新其他只读文本
textBox1.Text = System.DateTime.Now.ToString("HH:mm:ss");
textBox2.Text = Global.Connecting ? "已经连接" : "未连接";
//更新按钮文本
button1.Text = plc.IsConnected ? "断开连接" : "连接PLC";
}
private void UpdateReadWriteUI()
{
_isPLCUpdate = true;
foreach (var textBoxName in readWriteTextBoxNames)
{
// 只更新未被用户修改的控件
if (!userModfiedFlags[textBoxName] && !isEditingFlags[textBoxName])
{
TextBox? textBox = Controls.Find(textBoxName, true).FirstOrDefault() as TextBox;
if (textBox != null)
{
// 获取当前PLC值
string? plcValue = GetPLCValueByTextBoxName(textBoxName);
// 只有当值变化时才更新UI
if (textBox.Text != plcValue)
{
textBox.Text = plcValue;
}
}
}
}
_isPLCUpdate = false;
}
private string? GetPLCValueByTextBoxName(string textBoxName)
{
lock (_lockObject)
{
return textBoxName switch
{
nameof(textBox14) => db1Write.Bool1.ToString(),
nameof(textBox15) => db1Write.Bool2.ToString(),
nameof(textBox16) => db1Write.Bool3.ToString(),
nameof(textBox17) => db1Write.Bool4.ToString(),
nameof(textBox18) => db1Write.Byte1.ToString(),
nameof(textBox19) => db1Write.Byte2.ToString(),
nameof(textBox20) => db1Write.Int.ToString(),
nameof(textBox21) => db1Write.Dint.ToString(),
nameof(textBox22) => db1Write.Real.ToString("0.####"), // 控制精度
nameof(textBox23) => db1Write.String,
nameof(textBox24) => db1Write.Word.ToString(),
_ => null
};
}
}
private void ClearDataDisplay()
{
// 只清除UI显示,不重置状态
textBox3.Text = string.Empty;
textBox4.Text = string.Empty;
textBox5.Text = string.Empty;
textBox6.Text = string.Empty;
textBox7.Text = string.Empty;
textBox8.Text = string.Empty;
textBox9.Text = string.Empty;
textBox10.Text = string.Empty;
textBox11.Text = string.Empty;
textBox12.Text = string.Empty;
textBox13.Text = string.Empty;
textBox14.Text = string.Empty;
textBox15.Text = string.Empty;
textBox16.Text = string.Empty;
textBox17.Text = string.Empty;
textBox18.Text = string.Empty;
textBox19.Text = string.Empty;
textBox20.Text = string.Empty;
textBox21.Text = string.Empty;
textBox22.Text = string.Empty;
textBox23.Text = string.Empty;
textBox24.Text = string.Empty;
richTextBox1.Text = string.Empty;
}
}
}
Form1.Designer.cs
//Form1.Designer.cs
namespace S7ComTest
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
button1 = new Button();
label1 = new Label();
label2 = new Label();
textBox1 = new TextBox();
textBox2 = new TextBox();
label3 = new Label();
label5 = new Label();
textBox3 = new TextBox();
textBox4 = new TextBox();
label6 = new Label();
textBox5 = new TextBox();
label7 = new Label();
textBox6 = new TextBox();
label8 = new Label();
textBox7 = new TextBox();
label9 = new Label();
textBox8 = new TextBox();
label10 = new Label();
textBox9 = new TextBox();
label11 = new Label();
textBox10 = new TextBox();
label12 = new Label();
textBox11 = new TextBox();
label13 = new Label();
textBox12 = new TextBox();
label14 = new Label();
textBox13 = new TextBox();
label4 = new Label();
textBox14 = new TextBox();
label15 = new Label();
textBox15 = new TextBox();
label16 = new Label();
textBox16 = new TextBox();
label17 = new Label();
textBox17 = new TextBox();
label18 = new Label();
textBox18 = new TextBox();
label19 = new Label();
textBox19 = new TextBox();
label20 = new Label();
textBox20 = new TextBox();
label21 = new Label();
textBox21 = new TextBox();
label22 = new Label();
textBox22 = new TextBox();
label23 = new Label();
textBox23 = new TextBox();
label24 = new Label();
textBox24 = new TextBox();
label25 = new Label();
label26 = new Label();
richTextBox1 = new RichTextBox();
SuspendLayout();
//
// button1
//
button1.Location = new Point(283, 38);
button1.Name = "button1";
button1.Size = new Size(141, 61);
button1.TabIndex = 0;
button1.Text = "启用连接";
button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click;
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(20, 79);
label1.Name = "label1";
label1.Size = new Size(69, 20);
label1.TabIndex = 2;
label1.Text = "连接状态";
//
// label2
//
label2.AutoSize = true;
label2.Location = new Point(20, 38);
label2.Name = "label2";
label2.Size = new Size(69, 20);
label2.TabIndex = 3;
label2.Text = "当前时间";
//
// textBox1
//
textBox1.Location = new Point(107, 77);
textBox1.Name = "textBox1";
textBox1.ReadOnly = true;
textBox1.Size = new Size(121, 27);
textBox1.TabIndex = 4;
//
// textBox2
//
textBox2.Location = new Point(107, 35);
textBox2.Name = "textBox2";
textBox2.ReadOnly = true;
textBox2.Size = new Size(121, 27);
textBox2.TabIndex = 5;
//
// label3
//
label3.AutoSize = true;
label3.Location = new Point(20, 123);
label3.Name = "label3";
label3.Size = new Size(39, 20);
label3.TabIndex = 6;
label3.Text = "读取";
//
// label5
//
label5.AutoSize = true;
label5.Location = new Point(20, 165);
label5.Name = "label5";
label5.Size = new Size(59, 20);
label5.TabIndex = 8;
label5.Text = "BOOL1";
//
// textBox3
//
textBox3.Location = new Point(107, 162);
textBox3.Name = "textBox3";
textBox3.ReadOnly = true;
textBox3.Size = new Size(121, 27);
textBox3.TabIndex = 9;
//
// textBox4
//
textBox4.Location = new Point(107, 199);
textBox4.Name = "textBox4";
textBox4.ReadOnly = true;
textBox4.Size = new Size(121, 27);
textBox4.TabIndex = 11;
//
// label6
//
label6.AutoSize = true;
label6.Location = new Point(20, 202);
label6.Name = "label6";
label6.Size = new Size(59, 20);
label6.TabIndex = 10;
label6.Text = "BOOL2";
//
// textBox5
//
textBox5.Location = new Point(107, 236);
textBox5.Name = "textBox5";
textBox5.ReadOnly = true;
textBox5.Size = new Size(121, 27);
textBox5.TabIndex = 13;
//
// label7
//
label7.AutoSize = true;
label7.Location = new Point(20, 239);
label7.Name = "label7";
label7.Size = new Size(59, 20);
label7.TabIndex = 12;
label7.Text = "BOOL3";
//
// textBox6
//
textBox6.Location = new Point(107, 273);
textBox6.Name = "textBox6";
textBox6.ReadOnly = true;
textBox6.Size = new Size(121, 27);
textBox6.TabIndex = 15;
//
// label8
//
label8.AutoSize = true;
label8.Location = new Point(20, 276);
label8.Name = "label8";
label8.Size = new Size(59, 20);
label8.TabIndex = 14;
label8.Text = "BOOL4";
//
// textBox7
//
textBox7.Location = new Point(107, 310);
textBox7.Name = "textBox7";
textBox7.ReadOnly = true;
textBox7.Size = new Size(121, 27);
textBox7.TabIndex = 17;
//
// label9
//
label9.AutoSize = true;
label9.Location = new Point(20, 313);
label9.Name = "label9";
label9.Size = new Size(53, 20);
label9.TabIndex = 16;
label9.Text = "BYTE1";
//
// textBox8
//
textBox8.Location = new Point(107, 347);
textBox8.Name = "textBox8";
textBox8.ReadOnly = true;
textBox8.Size = new Size(121, 27);
textBox8.TabIndex = 19;
//
// label10
//
label10.AutoSize = true;
label10.Location = new Point(20, 350);
label10.Name = "label10";
label10.Size = new Size(53, 20);
label10.TabIndex = 18;
label10.Text = "BYTE2";
//
// textBox9
//
textBox9.Location = new Point(345, 162);
textBox9.Name = "textBox9";
textBox9.ReadOnly = true;
textBox9.Size = new Size(121, 27);
textBox9.TabIndex = 21;
//
// label11
//
label11.AutoSize = true;
label11.Location = new Point(258, 165);
label11.Name = "label11";
label11.Size = new Size(34, 20);
label11.TabIndex = 20;
label11.Text = "INT";
//
// textBox10
//
textBox10.Location = new Point(345, 199);
textBox10.Name = "textBox10";
textBox10.ReadOnly = true;
textBox10.Size = new Size(121, 27);
textBox10.TabIndex = 23;
//
// label12
//
label12.AutoSize = true;
label12.Location = new Point(258, 202);
label12.Name = "label12";
label12.Size = new Size(45, 20);
label12.TabIndex = 22;
label12.Text = "DINT";
//
// textBox11
//
textBox11.Location = new Point(345, 236);
textBox11.Name = "textBox11";
textBox11.ReadOnly = true;
textBox11.Size = new Size(121, 27);
textBox11.TabIndex = 25;
//
// label13
//
label13.AutoSize = true;
label13.Location = new Point(258, 239);
label13.Name = "label13";
label13.Size = new Size(46, 20);
label13.TabIndex = 24;
label13.Text = "REAL";
//
// textBox12
//
textBox12.Location = new Point(345, 273);
textBox12.Name = "textBox12";
textBox12.ReadOnly = true;
textBox12.Size = new Size(121, 27);
textBox12.TabIndex = 27;
//
// label14
//
label14.AutoSize = true;
label14.Location = new Point(258, 276);
label14.Name = "label14";
label14.Size = new Size(64, 20);
label14.TabIndex = 26;
label14.Text = "STRING";
//
// textBox13
//
textBox13.Location = new Point(345, 310);
textBox13.Name = "textBox13";
textBox13.ReadOnly = true;
textBox13.Size = new Size(121, 27);
textBox13.TabIndex = 29;
//
// label4
//
label4.AutoSize = true;
label4.Location = new Point(258, 313);
label4.Name = "label4";
label4.Size = new Size(57, 20);
label4.TabIndex = 28;
label4.Text = "WORD";
//
// textBox14
//
textBox14.Location = new Point(590, 162);
textBox14.Name = "textBox14";
textBox14.Size = new Size(121, 27);
textBox14.TabIndex = 52;
//
// label15
//
label15.AutoSize = true;
label15.Location = new Point(741, 313);
label15.Name = "label15";
label15.Size = new Size(57, 20);
label15.TabIndex = 51;
label15.Text = "WORD";
//
// textBox15
//
textBox15.Location = new Point(590, 199);
textBox15.Name = "textBox15";
textBox15.Size = new Size(121, 27);
textBox15.TabIndex = 50;
//
// label16
//
label16.AutoSize = true;
label16.Location = new Point(741, 276);
label16.Name = "label16";
label16.Size = new Size(64, 20);
label16.TabIndex = 49;
label16.Text = "STRING";
//
// textBox16
//
textBox16.Location = new Point(590, 236);
textBox16.Name = "textBox16";
textBox16.Size = new Size(121, 27);
textBox16.TabIndex = 48;
//
// label17
//
label17.AutoSize = true;
label17.Location = new Point(741, 239);
label17.Name = "label17";
label17.Size = new Size(46, 20);
label17.TabIndex = 47;
label17.Text = "REAL";
//
// textBox17
//
textBox17.Location = new Point(590, 273);
textBox17.Name = "textBox17";
textBox17.Size = new Size(121, 27);
textBox17.TabIndex = 46;
//
// label18
//
label18.AutoSize = true;
label18.Location = new Point(741, 202);
label18.Name = "label18";
label18.Size = new Size(45, 20);
label18.TabIndex = 45;
label18.Text = "DINT";
//
// textBox18
//
textBox18.Location = new Point(590, 310);
textBox18.Name = "textBox18";
textBox18.Size = new Size(121, 27);
textBox18.TabIndex = 44;
//
// label19
//
label19.AutoSize = true;
label19.Location = new Point(741, 165);
label19.Name = "label19";
label19.Size = new Size(34, 20);
label19.TabIndex = 43;
label19.Text = "INT";
//
// textBox19
//
textBox19.Location = new Point(590, 347);
textBox19.Name = "textBox19";
textBox19.Size = new Size(121, 27);
textBox19.TabIndex = 42;
//
// label20
//
label20.AutoSize = true;
label20.Location = new Point(503, 350);
label20.Name = "label20";
label20.Size = new Size(53, 20);
label20.TabIndex = 41;
label20.Text = "BYTE2";
//
// textBox20
//
textBox20.Location = new Point(828, 162);
textBox20.Name = "textBox20";
textBox20.Size = new Size(121, 27);
textBox20.TabIndex = 40;
//
// label21
//
label21.AutoSize = true;
label21.Location = new Point(503, 313);
label21.Name = "label21";
label21.Size = new Size(53, 20);
label21.TabIndex = 39;
label21.Text = "BYTE1";
//
// textBox21
//
textBox21.Location = new Point(828, 199);
textBox21.Name = "textBox21";
textBox21.Size = new Size(121, 27);
textBox21.TabIndex = 38;
//
// label22
//
label22.AutoSize = true;
label22.Location = new Point(503, 276);
label22.Name = "label22";
label22.Size = new Size(59, 20);
label22.TabIndex = 37;
label22.Text = "BOOL4";
//
// textBox22
//
textBox22.Location = new Point(828, 236);
textBox22.Name = "textBox22";
textBox22.Size = new Size(121, 27);
textBox22.TabIndex = 36;
//
// label23
//
label23.AutoSize = true;
label23.Location = new Point(503, 239);
label23.Name = "label23";
label23.Size = new Size(59, 20);
label23.TabIndex = 35;
label23.Text = "BOOL3";
//
// textBox23
//
textBox23.Location = new Point(828, 273);
textBox23.Name = "textBox23";
textBox23.Size = new Size(121, 27);
textBox23.TabIndex = 34;
//
// label24
//
label24.AutoSize = true;
label24.Location = new Point(503, 202);
label24.Name = "label24";
label24.Size = new Size(59, 20);
label24.TabIndex = 33;
label24.Text = "BOOL2";
//
// textBox24
//
textBox24.Location = new Point(828, 310);
textBox24.Name = "textBox24";
textBox24.Size = new Size(121, 27);
textBox24.TabIndex = 32;
//
// label25
//
label25.AutoSize = true;
label25.Location = new Point(503, 165);
label25.Name = "label25";
label25.Size = new Size(59, 20);
label25.TabIndex = 31;
label25.Text = "BOOL1";
//
// label26
//
label26.AutoSize = true;
label26.Location = new Point(503, 123);
label26.Name = "label26";
label26.Size = new Size(39, 20);
label26.TabIndex = 30;
label26.Text = "写入";
//
// richTextBox1
//
richTextBox1.Location = new Point(-1, 390);
richTextBox1.Name = "richTextBox1";
richTextBox1.ReadOnly = true;
richTextBox1.Size = new Size(983, 150);
richTextBox1.TabIndex = 54;
richTextBox1.Text = "";
//
// Form1
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(985, 541);
Controls.Add(richTextBox1);
Controls.Add(textBox14);
Controls.Add(label15);
Controls.Add(textBox15);
Controls.Add(label16);
Controls.Add(textBox16);
Controls.Add(label17);
Controls.Add(textBox17);
Controls.Add(label18);
Controls.Add(textBox18);
Controls.Add(label19);
Controls.Add(textBox19);
Controls.Add(label20);
Controls.Add(textBox20);
Controls.Add(label21);
Controls.Add(textBox21);
Controls.Add(label22);
Controls.Add(textBox22);
Controls.Add(label23);
Controls.Add(textBox23);
Controls.Add(label24);
Controls.Add(textBox24);
Controls.Add(label25);
Controls.Add(label26);
Controls.Add(textBox13);
Controls.Add(label4);
Controls.Add(textBox12);
Controls.Add(label14);
Controls.Add(textBox11);
Controls.Add(label13);
Controls.Add(textBox10);
Controls.Add(label12);
Controls.Add(textBox9);
Controls.Add(label11);
Controls.Add(textBox8);
Controls.Add(label10);
Controls.Add(textBox7);
Controls.Add(label9);
Controls.Add(textBox6);
Controls.Add(label8);
Controls.Add(textBox5);
Controls.Add(label7);
Controls.Add(textBox4);
Controls.Add(label6);
Controls.Add(textBox3);
Controls.Add(label5);
Controls.Add(label3);
Controls.Add(textBox2);
Controls.Add(textBox1);
Controls.Add(label2);
Controls.Add(label1);
Controls.Add(button1);
Name = "Form1";
Text = "S7通讯测试";
ResumeLayout(false);
PerformLayout();
}
#endregion
private Button button1;
private Label label1;
private Label label2;
private TextBox textBox1;
private TextBox textBox2;
private Label label3;
private Label label5;
private TextBox textBox3;
private TextBox textBox4;
private Label label6;
private TextBox textBox5;
private Label label7;
private TextBox textBox6;
private Label label8;
private TextBox textBox7;
private Label label9;
private TextBox textBox8;
private Label label10;
private TextBox textBox9;
private Label label11;
private TextBox textBox10;
private Label label12;
private TextBox textBox11;
private Label label13;
private TextBox textBox12;
private Label label14;
private TextBox textBox13;
private Label label4;
private TextBox textBox14;
private Label label15;
private TextBox textBox15;
private Label label16;
private TextBox textBox16;
private Label label17;
private TextBox textBox17;
private Label label18;
private TextBox textBox18;
private Label label19;
private TextBox textBox19;
private Label label20;
private TextBox textBox20;
private Label label21;
private TextBox textBox21;
private Label label22;
private TextBox textBox22;
private Label label23;
private TextBox textBox23;
private Label label24;
private TextBox textBox24;
private Label label25;
private Label label26;
private RichTextBox richTextBox1;
}
}
FromS7.cs
//FromS7.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace S7ComTest.Types
{
public class FromS7
{
public class Bit
{
/// <summary>
/// 将布尔值写入到字节跨度的指定位置(按位操作)
/// </summary>
/// <param name="value">要写入的布尔值</param>
/// <param name="destination">目标字节跨度</param>
/// <param name="byteIndex">字节跨度中的索引位置</param>
/// <param name="bitIndex">字节内的位索引(0-7),0表示最低位(最右侧),7表示最高位</param>
/// <exception cref="ArgumentOutOfRangeException">
/// 当byteIndex超出跨度范围,或bitIndex超出0-7范围时抛出
/// </exception>
/// <remarks>
/// 此方法会直接修改目标跨度中的指定字节。
/// 位操作原理:
/// - 设置特定位:使用按位或 (targetByte | (1 << bitIndex))
/// - 清除特定位:使用按位与 (targetByte & ~(1 << bitIndex))
/// </remarks>
public static void ToByteArray(bool value, Span<byte> destination, int byteIndex, int bitIndex)
{
// 验证参数有效性
if (byteIndex < 0 || byteIndex >= destination.Length)
throw new ArgumentOutOfRangeException(nameof(byteIndex),
$"字节索引必须在0-{destination.Length - 1}范围内");
if (bitIndex < 0 || bitIndex > 7)
throw new ArgumentOutOfRangeException(nameof(bitIndex), "位索引必须在0-7范围内");
// 获取目标字节
ref byte targetByte = ref destination[byteIndex];
// 修改位值
if (value)
{
// 设置指定位置的位为1
targetByte = (byte)(targetByte | (1 << bitIndex));
}
else
{
// 清除指定位置的位为0
targetByte = (byte)(targetByte & ~(1 << bitIndex));
}
}
}
public class Int
{
/// <summary>
/// 将 short (Int16) 转换为 S7 Int 格式,并写入到指定的字节跨度中
/// </summary>
/// <param name="value">要转换的 Int16 值</param>
/// <param name="destination">目标字节跨度,必须至少有 2 字节的空间</param>
/// <exception cref="ArgumentException">当目标跨度长度不足时抛出</exception>
public static void ToByteArray(Int16 value, Span<byte> destination)
{
if (destination.Length < 2)
throw new ArgumentException("目标跨度长度必须至少为 2 字节。", nameof(destination));
// 大端字节序(高字节在前)
destination[0] = (byte)(value >> 8); // 高字节
destination[1] = (byte)value; // 低字节
}
}
public class Dint
{
/// <summary>
/// 将 int (Int32) 转换为 S7 DInt 格式,并写入到指定的字节跨度中
/// </summary>
/// <param name="value">要转换的 Int32 值</param>
/// <param name="destination">目标字节跨度,必须至少有 4 字节的空间</param>
/// <exception cref="ArgumentException">当目标跨度长度不足时抛出</exception>
public static void ToByteArray(Int32 value, Span<byte> destination)
{
if (destination.Length < 4)
throw new ArgumentException("目标跨度长度必须至少为 4 字节。", nameof(destination));
// 大端字节序(高字节在前)
destination[0] = (byte)(value >> 24); // 最高字节
destination[1] = (byte)(value >> 16); // 次高字节
destination[2] = (byte)(value >> 8); // 次低字节
destination[3] = (byte)value; // 最低字节
}
}
public class Real
{
/// 将 float 转换为 S7 Real 格式,并写入到指定的字节跨度中
/// </summary>
/// <param name="value">要转换的 float 值</param>
/// <param name="destination">目标字节跨度,必须至少有 4 字节的空间</param>
/// <exception cref="ArgumentException">当目标跨度长度不足时抛出</exception>
public static void ToByteArray(float value, Span<byte> destination)
{
if (destination.Length < 4)
throw new ArgumentException("目标跨度长度必须至少为 4 字节。", nameof(destination));
// 将 float 转换为字节(平台原生字节序)
byte[] temp = BitConverter.GetBytes(value);
// 根据平台字节序调整
if (BitConverter.IsLittleEndian)
{
// S7 使用大端字节序,而当前平台是小端,需要反转
destination[0] = temp[3];
destination[1] = temp[2];
destination[2] = temp[1];
destination[3] = temp[0];
}
else
{
// 平台字节序与 S7 一致(大端),直接复制
temp.AsSpan().CopyTo(destination);
}
}
}
public class S7String
{
private static Encoding stringEncoding = Encoding.ASCII;
/// <summary>
/// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default)
/// </summary>
/// <exception cref="ArgumentNullException">StringEncoding must not be null</exception>
public static Encoding StringEncoding
{
get => stringEncoding;
set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding));
}
/// <summary>
/// 将字符串转换为S7协议格式的字符串(带2字节头部),并写入目标字节跨度
/// S7字符串格式:
/// - 第1个字节:预留长度(字符串最多可包含的字符数)
/// - 第2个字节:实际长度(当前字符串的字符数)
/// - 后续字节:字符串内容的字节表示
/// </summary>
/// <param name="value">要转换的字符串,不能为null</param>
/// <param name="destinationSpan">目标字节跨度,用于存储转换后的S7字符串</param>
/// <param name="startIndex">在目标跨度中开始写入的起始索引</param>
/// <exception cref="ArgumentNullException">当value为null时抛出</exception>
/// <exception cref="ArgumentException">
/// 当预留长度超过254字符,或字符串实际长度超过预留长度时抛出
/// </exception>
public static void ToByteArray(string? value, Span<byte> destinationSpan, int startIndex)
{
// 验证输入字符串不为null
if (value is null)
{
throw new ArgumentNullException(nameof(value), "输入字符串不能为null");
}
// 计算预留长度(使用字符串实际长度)
int reservedLength = value.Length;
// 检查预留长度是否超出S7协议限制(最大254字符)
if (reservedLength > 254)
{
throw new ArgumentException($"S7协议支持的最大字符串长度为254字符,当前字符串长度为{reservedLength}");
}
// 将字符串编码为字节数组
byte[] bytes = StringEncoding.GetBytes(value);
// 验证编码后的字节长度是否符合预留长度
if (bytes.Length > reservedLength)
{
throw new ArgumentException($"编码后的字符串长度({bytes.Length})超过了预留长度({reservedLength})");
}
// 写入S7字符串头部信息
destinationSpan[startIndex] = (byte)reservedLength; // 预留长度
destinationSpan[startIndex + 1] = (byte)bytes.Length; // 实际长度
// 写入字符串内容(从startIndex+2位置开始)
bytes.AsSpan().CopyTo(destinationSpan.Slice(startIndex + 2));
}
}
public class Word
{
/// <summary>
/// 将 ushort (UInt16) 转换为 S7 Word 格式,并写入到指定的字节跨度中
/// </summary>
/// <param name="value">要转换的 UInt16 值</param>
/// <param name="destination">目标字节跨度,必须至少有 2 字节的空间</param>
/// <exception cref="ArgumentException">当目标跨度长度不足时抛出</exception>
public static void ToByteArray(UInt16 value, Span<byte> destination)
{
if (destination.Length < 2)
throw new ArgumentException("目标跨度长度必须至少为 2 字节。", nameof(destination));
// 大端字节序(高字节在前)
destination[0] = (byte)(value >> 8); // 高字节
destination[1] = (byte)value; // 低字节
}
}
public class Dword
{
/// <summary>
/// 将 uint (UInt32) 转换为 S7 DWord 格式,并写入到指定的字节跨度中
/// </summary>
/// <param name="value">要转换的 UInt32 值</param>
/// <param name="destination">目标字节跨度,必须至少有 4 字节的空间</param>
/// <exception cref="ArgumentException">当目标跨度长度不足时抛出</exception>
public static void ToByteArray(UInt32 value, Span<byte> destination)
{
if (destination.Length < 4)
throw new ArgumentException("目标跨度长度必须至少为 4 字节。", nameof(destination));
// 大端字节序(高字节在前)
destination[0] = (byte)(value >> 24); // 最高字节
destination[1] = (byte)(value >> 16); // 次高字节
destination[2] = (byte)(value >> 8); // 次低字节
destination[3] = (byte)value; // 最低字节
}
}
}
}
NumberFromString.cs
//NumberFromString.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace S7ComTest.Types
{
public static class numberFromString
{
public static bool ParseBool(string text, Action<string>? errorCallback = null)
{
if (string.IsNullOrWhiteSpace(text))
return false;
// 标准布尔解析
if (bool.TryParse(text, out bool result))
return result;
// 扩展格式支持
string lowerText = text.ToLowerInvariant();
switch (lowerText)
{
case "1":
case "on":
case "yes":
case "true":
case "enabled":
return true;
case "0":
case "off":
case "no":
case "false":
case "disabled":
return false;
default:
errorCallback?.Invoke($"无效的布尔值: '{text}',已使用默认值 false");
return false;
}
}
public static byte ParseByte(string text, Action<string>? errorCallback = null)
{
if (string.IsNullOrWhiteSpace(text))
return 0;
// 处理十六进制格式
if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// 修复:使用不同的变量名避免冲突
if (byte.TryParse(text.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out byte hexResult))
return hexResult;
}
// 处理十进制格式
if (byte.TryParse(text, out byte decResult))
return decResult;
errorCallback?.Invoke($"无效的字节值: '{text}',已使用默认值 0");
return 0;
}
public static short ParseInt(string text, Action<string>? errorCallback = null)
{
if (string.IsNullOrWhiteSpace(text))
return 0;
// 处理十六进制格式
if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// 修复:使用不同的变量名避免冲突
if (short.TryParse(text.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out short hexResult))
return hexResult;
}
// 处理十进制格式
if (short.TryParse(text, out short decResult))
return decResult;
errorCallback?.Invoke($"无效的整数: '{text}',已使用默认值 0");
return 0;
}
public static int ParseDint(string text, Action<string>? errorCallback = null)
{
if (string.IsNullOrWhiteSpace(text))
return 0;
// 处理十六进制格式
if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// 修复:使用不同的变量名避免冲突
if (int.TryParse(text.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out int hexResult))
return hexResult;
}
// 处理十进制格式
if (int.TryParse(text, out int decResult))
return decResult;
errorCallback?.Invoke($"无效的长整数: '{text}',已使用默认值 0");
return 0;
}
public static float ParseReal(string text, Action<string>? errorCallback = null)
{
if (string.IsNullOrWhiteSpace(text))
return 0;
// 使用固定的不变文化格式解析浮点数,确保小数点始终为'.'
if (float.TryParse(text, System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out float result))
return result;
errorCallback?.Invoke($"无效的浮点数: '{text}',已使用默认值 0");
return 0;
}
public static ushort ParseWord(string text, Action<string>? errorCallback = null)
{
if (string.IsNullOrWhiteSpace(text))
return 0;
// 处理十六进制格式
if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// 修复:使用不同的变量名避免冲突
if (ushort.TryParse(text.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out ushort hexResult))
return hexResult;
}
// 处理十进制格式
if (ushort.TryParse(text, out ushort decResult))
return decResult;
errorCallback?.Invoke($"无效的字值: '{text}',已使用默认值 0");
return 0;
}
}
}
以上代码实现与S7-1200通讯,左边是只读取,右边是读写区,这两个区域每隔1S从PLC读取数据,存储在缓存中,再有程序判断是否跟上次数据有变化,若有,则更新UI,若没有,则不更新UI。读写区还可以写入数据,当写入数据的过程时,读写区不更新相应的数据,直到写入完成。
注意,西门子1200需要开启PUT/GET通讯允许。
在Form1中,很多方法都有注释,请自行理解,后续有空的时候,再分析具体方法。
另外,在下载下来的C#版本的S7.NET源码可以自行修改编辑,编辑完再重新生成,然后在对应框架下找到S7.Net.dll、S7.Net.xml复制到指定地方替换即可。
源包可以自行去网上下载。这里不建议修改它,如果需要添加转换方法,可以自行在项目中创建,比如,我自己创建的FromS7.cs、NumberFromString.cs //包含在Types文件夹中,是我自己创建的数据处理的方法、类等的命名空间。
修改后生成的.dll文件所在位置
替换位置
到此结束,整个过程其实很简单,通讯配置也很简单。主要是这个处理逻辑、多线程等比较麻烦,需要自己去建。如果C#水平不咋地,可以直接把这个搬过去,自己再扩充。这个代码里,采用的是批量通讯,只需要放在附近,规划好自己,就能拿来用了。这个只是做简单的画面和数据监控,当然,如果需要开发更丰富复杂的画面,也可以采用WPF应用程序,S7.NET配置基本一致。区别就在两个应用程序的语法上,请各位自行选择。