初识上位机(下):C#读写PLC数据块数据
<p>大家好,我是Edison。</p><p>作为一个工业自动化领域的程序员,不懂点PLC和上位机,貌似有点说不过去。这里我用两篇小文带你快速进入上位机开发领域。后续,我会考虑再出一个系列文章一起玩工控上位机。</p>
<p>上一篇:搭建PLC模拟仿真环境</p>
<h1><strong>复习一下</strong></h1>
<p>在上一篇中,我们通过PLCSIM Advanced软件创建了一个虚拟的西门子S7-1500 PLC如下所示:</p>
<p><img src="https://img2024.cnblogs.com/blog/381412/202405/381412-20240515224914047-1560301085.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>然后,我们创建了一个博途的自动化项目,和我们的虚拟PLC进行了组态。在编译完成后,我们创建的数据块中的数据字段就得到了偏移量,如下图所示,0,2,4, 260就是所谓的偏移量,会在后面用到。</p>
<p><img src="https://img2024.cnblogs.com/blog/381412/202405/381412-20240515224936998-1760037141.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<h1 data-pid="8wYtSw7a"><strong>创建Windows Form项目</strong></h1>
<p>这里开始我们就开始使用C#创建一个Windows Form项目,然后通过S7NetPlus库来连接PLC,并读取和写入数据块中的数据,这是一个典型的上位机数据采集的场景。这里我们创建一个.NET Framework 4.8的Windows Form项目,并拖控件完成一个如下图所示的窗体应用界面:</p>
<p><img src="https://img2024.cnblogs.com/blog/381412/202405/381412-20240515224957504-1058533270.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>这个窗体提供了连接和断开PLC,以及读取 和 写入 文本框中的数据,接下来我们就来实现这几个功能。</p>
<h1 data-pid="8wYtSw7a"><strong>实现PLC的连接与断开</strong></h1>
<p data-pid="8wYtSw7a">要实现S7 PLC的连接和操作,目前已经有很多较为成熟的组件了,我们这里使用S7NetPlus组件,直接通过NuGet安装最新版本即可。</p>
<p><img src="https://img2024.cnblogs.com/blog/381412/202405/381412-20240515225013441-1149026389.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<p data-pid="8wYtSw7a">然后编写Connect按钮的Click事件如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">private static Plc s7Instance;
public MainForm()
{
InitializeComponent();
}
private void btnConnect_Click(object sender, System.EventArgs e)
{
if (btnConnect.Text == "Connect")
{
if (s7Instance == null)
s7Instance = new Plc(CpuType.S71500, txtPlcIPAddress.Text.Trim(), 0, 1);
s7Instance.Open();
btnConnect.Text = "Disconnect";
}
else
{
s7Instance.Close();
btnConnect.Text = "Connect";
txtBool01.Clear();
txtInt01.Clear();
txtStr01.Clear();
txtStr02.Clear();
}
}
</pre>
</div>
<h1><strong>实现PLC数据块的读取</strong></h1>
<p>由于我们在博途项目中设置的数据块是DB01,且只有4个字段,所以这里我们编写ReadData按钮的Click事件如下,它通过指定参数读取到指定类型的数据并绑定到文本框的Text中。 </p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">private void btnReadData_Click(object sender, System.EventArgs e)
{
if (s7Instance == null || !s7Instance.IsConnected)
{
MessageBox.Show("Your PLC is not connected now!", "Error", MessageBoxButtons.OK);
return;
}
// bool
var boolData = (bool)s7Instance.Read(DataType.DataBlock, 1, 0, VarType.Bit, 1);
txtBool01.Text = boolData ? "1" : "0";
// int
var intData = (short)s7Instance.Read(DataType.DataBlock, 1, 2, VarType.Int, 1);
txtInt01.Text = intData.ToString();
// string
var count = (byte)s7Instance.Read(DataType.DataBlock, 1, 4 + 1, VarType.Byte, 1); // +1表示读取偏移值的长度
var str01Data = Encoding.Default.GetString(s7Instance.ReadBytes(DataType.DataBlock, 1, 4 + 2, count)); // +2表示读取偏移值(跳过)的字符
txtStr01.Text = str01Data;
// wstring
var str02Data = (string)s7Instance.Read(DataType.DataBlock, 1, 260, VarType.S7WString, 254);
txtStr02.Text = str02Data;
}</pre>
</div>
<p>要点解读:</p>
<p>(1)针对bool和int类型,我们可以直接通过Read方法快速读取到,但需要告诉PLC准确的读写位置和数据类型,主要是偏移量一定要给正确。</p>
<blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="36" data-source-title="">
<p>Read方法的参数分别为数据块类型,数据块,偏移量,读取类型,读取长度</p>
</blockquote>
<p>(2)针对string和wstring类型,就稍微麻烦一些了:针对string,需要先获取string值的所占长度。再拿到具体byte值。转换为utf8格式的ascci码,具体代码中有体现。</p>
<p><strong>+1 表示获取到长度</strong></p>
<p><strong>+2 表示获取到跳过偏移长度的字符</strong></p>
<p>特别注意:string类型只能存储ascci码,需要注意,<strong>不能存储中文</strong>!</p>
<p>针对wstring,稍微简单点,但是需要注意的是<strong>获取的字符需要为254个</strong>,因为符号占用了4个字节。</p>
<h1 data-pid="8wYtSw7a"><strong>实现PLC数据块的写入</strong></h1>
<p>和读取一样,通过Write方法即可轻松实现写入,但针对string和wstring仍然是复杂一些,这里我封装了一个S7DataWriter的静态类,提供了两个方法来获取要写入的bytes,因为它无法直接接收C#程序中的string类型。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">public static class S7DataWriter
{
/// <summary>
/// 获取西门子PLC字符串数组--String类型
/// </summary>
public static byte[] GetPlcStringByteArray(string str)
{
byte[] value = Encoding.Default.GetBytes(str);
byte[] head = new byte;
head = Convert.ToByte(254);
head = Convert.ToByte(str.Length);
value = head.Concat(value).ToArray();
return value;
}
/// <summary>
/// 获取西门子PLC字符串数组--WString类型
/// </summary>
public static byte[] GetPlcWStringByteArray(string str)
{
byte[] value = Encoding.BigEndianUnicode.GetBytes(str);
byte[] head = BitConverter.GetBytes((short)508);
byte[] length = BitConverter.GetBytes((short)str.Length);
Array.Reverse(head);
Array.Reverse(length);
head = head.Concat(length).ToArray();
value = head.Concat(value).ToArray();
return value;
}
}
</pre>
</div>
<p>然后,我们就可以编写Write Data按钮的Click事件了:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">private void btnWriteData_Click(object sender, System.EventArgs e)
{
if (s7Instance == null || !s7Instance.IsConnected)
{
MessageBox.Show("Your PLC is not connected now!", "Error", MessageBoxButtons.OK);
return;
}
// bool
var boolValue = txtBool01.Text.Trim() == "1" ? true : false;
s7Instance.Write(DataType.DataBlock, 1, 0, boolValue);
// int
var intValue = Convert.ToInt16(txtInt01.Text);
s7Instance.Write(DataType.DataBlock, 1, 2, intValue);
// string
s7Instance.Write(DataType.DataBlock, 1, 4, S7DataWriter.GetPlcStringByteArray(txtStr01.Text.Trim()));
// wstring
s7Instance.Write(DataType.DataBlock, 1, 260, S7DataWriter.GetPlcWStringByteArray(txtStr02.Text.Trim()));
MessageBox.Show("Write data successfully!", "Info", MessageBoxButtons.OK);
}
</pre>
</div>
<h1 data-pid="8wYtSw7a"><strong>效果演示</strong></h1>
<p>和读取一样,通过Write方法即可轻松实现写入,但针对string和wstring仍然是复杂一些,这里我封装了一个S7DataWriter的静态类,提供了两个方法来获取要写入的bytes,因为它无法直接接收C#程序中的string类型。</p>
<p>(1)读取数据</p>
<p><img src="https://img2024.cnblogs.com/blog/381412/202405/381412-20240515225225487-669171469.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>(2)写入数据</p>
<p><img src="https://img2024.cnblogs.com/blog/381412/202405/381412-20240515225237498-1664690364.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<h1 data-pid="8wYtSw7a"><strong>小结</strong></h1>
<p>本文通过使用C#开发了一个简单的WindowsForm窗体程序,实现了S7 PLC的连接、数据读取和写入。虽然只是一个简单的Demo,但是从中可以看见上位机的基本思想,就是对PLC的数据采集和监控。当然,实现这个目的,不止S7协议一条路,我们还可以通过ModBus、OPC UA等协议,这些就留到后面的专题吧,如果你感兴趣的话,就保持关注哦!</p>
<h1 data-pid="8wYtSw7a"><strong>源码</strong></h1>
<p>GitHub:https://github.com/Coder-EdisonZhou/PLC-Connectors</p>
<p> </p>
<p style="text-align: center"><img src="https://images.cnblogs.com/cnblogs_com/edisonchou/1647700/o_200902144330EdisonTalk-Footer.jpg" alt="" style="width: 65%; border: 1px solid rgba(221, 221, 221, 1); border-radius: 3px; box-shadow: 0 4px 8px rgba(3, 27, 78, 0.12)"></p>
<div id="Copyright">
<p>作者:周旭龙</p>
<p>出处:https://edisonchou.cnblogs.com</p>
<p>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。</p>
</div>
</div>
<div id="MySignature" role="contentinfo">
<div align="center"><img border="0" src="http://service.t.sina.com.cn/widget/qmd/2068032061/d643d182/10.png"></div><br><br>
来源:https://www.cnblogs.com/edisontalk/p/-/industrial-control-plc-connector-02
頁:
[1]