PLC内部数据的传统读写方式是利用触摸屏组态。PLC可以和HMI轻易建立起连接并传输数据。而S7netplus为我们提供了另一种连接思路,让我们可以利用C#读写PLC内部的数据。这样做的优点是显而易见的:可以做出比WinCC更漂亮更强大的界面;可以对数据更好地利用;可以以更高频率获取PLC数据而不是10Hz等等。

上图是我用S7netplus做的调试小工具。虽然只有简单的功能,却可以在现场调试时省的抱着笔记本等博途启动。顺便吐槽一下西门子,自己的软件互相之间都不兼容,安装麻烦得要死。就算运气好都安上了,稍微差点的电脑还带不动,一启动就等很久。不过谁让人家是业界巨头呢,难用也只能忍着。
回到正题。S7netplus使用起来非常简单,但是它的文档不太好。只有英文版倒是问题不大,主要是不全,只介绍了一部分内容,还和实际操作不太一样。也可能是我没找到最新版的文档吧。
安装与连接
前提
硬件方面只需要用网线把PLC与电脑相连,处于同一网段即可。本次测试使用的是西门子S7-1200 1215C DC/DC/DC以及西门子Tp700 Comfort面板。
与PLC通讯前首先要让PLC处于可访问状态。设置方法如下:
第一步:打开博途,在项目树找到PLC打开属性。将保护设置为完全访问权限,并将下面连接机制的选项打钩。

第二步:在PLC下面依次打开每一个用到的DB块的属性。关闭“优化块的访问”这个选项。否则没有偏移量不能访问。点击确定后编译DB块即可。

更改完成之后需要在设备组态中编译并下载才能使设置生效。

S7netplus目前最新的版本是0.4.0,需要Visual Studio 2015及以上版本。框架要求.Net Framework 4.5.2。支持使用Nuget安装:
PM> Install-Package S7netplus -Version 0.4.0
安装好之后不需要其他操作,添加:
using S7.Net;
之后就可以用了。
连接
S7netplus支持的CPU包括:LOGO!0BA8,S7-200,S7-300,S7-400,S7-1200,S7-1500。
plc连接的定义为:
public Plc(CpuType cpu, string ip, short rack, short slot);
四个参数分别是:CPU类型,CPU的IP地址,CPU的机架号(Rack)与插槽号(Slot)。

连接与断开方式如下:
namespace s7netplus
{
public partial class Form1 : Form
{
private Plc plc = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
toolStripStatusLabel2.Text = "就绪";
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (plc.IsConnected)
{
plc.Close();
}
}
private void connbtn_Click(object sender, EventArgs e)
{
try
{
plc = new Plc(CpuType.S71200, "192.168.0.1", 0, 1);
plc.Open();
if (plc.IsConnected)
{
toolStripStatusLabel2.Text = "已建立连接";
}
else
{
toolStripStatusLabel2.Text = "连接失败";
}
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message);
}
}
private void cutbtn_Click(object sender, EventArgs e)
{
if (plc.IsConnected)
{
plc.Close();
toolStripStatusLabel2.Text = "连接已断开";
}
}
}
}
文档中的Open()
函数类型为ErrorCode
,可以使用ErrorCode state = plc.Open();
来获取连接的状态码。但在0.4.0版本实际使用中发现状态码仍然存在,但Open()
函数声明为void
。因为查不到更多资料,此处获取状态码的方式未知。
数据操作
数字读写
S7netplus提供了对五种数据的读写方法:Word,Int,DWord,DInt,Real。每一种类型需要不同的转换方式。具体方法如下:
private void readbtn_Click(object sender, EventArgs e)
{
if (plc == null)
{
MessageBox.Show("尚未建立与PLC的连接");
}
else
{
if (plc != null)
{
switch (typebox.Text)
{
case "Word":
ushort result1 = (ushort)plc.Read("DB1.DBW0");
readtxt.Text = result1.ToString();
break;
case "Int":
short result2 = ((ushort)plc.Read("DB1.DBW0")).ConvertToShort();
readtxt.Text = result2.ToString();
break;
case "DWord":
uint result3 = (uint)plc.Read("DB1.DBD0");
readtxt.Text = result3.ToString();
break;
case "DInt":
int result4 = ((uint)plc.Read("DB1.DBD0")).ConvertToInt();
readtxt.Text = result4.ToString();
break;
case "Real":
double result5 = ((uint)plc.Read("DB1.DBD0")).ConvertToFloat();
readtxt.Text = result5.ToString();
break;
default:
break;
}
toolStripStatusLabel2.Text = "读取成功";
}
}
}
private void writebtn_Click(object sender, EventArgs e)
{
if (plc == null)
{
MessageBox.Show("尚未建立与PLC的连接");
}
else
{
if (plc != null)
{
switch (typebox.Text)
{
case "Word":
ushort value1 = ushort.Parse(writetxt.Text);
plc.Write("DB1.DBW0", value1);
break;
case "Int":
short value2 = short.Parse(writetxt.Text);
plc.Write("DB1.DBW0", value2);
break;
case "DWord":
uint value3 = uint.Parse(writetxt.Text);
plc.Write("DB1.DBD0", value3);
break;
case "DInt":
int value4 = int.Parse(writetxt.Text);
plc.Write("DB1.DBD0", value4);
break;
case "Real":
double value5 = double.Parse(writetxt.Text);
plc.Write("DB1.DBD0", value5);
break;
default:
break;
}
toolStripStatusLabel2.Text = "数据已写入";
}
}
}
布尔读写
S7netplus并没有直接提供单个位的查询。但是提供了字节查询,并附带了直接从字节提取位的方法:
byte[] myByte = plc.ReadBytes(...);
byte myByte[0] = 5; // 0000 0101
myByte.SelectBit(0) // true
myByte.SelectBit(1) // false
读写的定义如下:
public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count);
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value);
int db
默认为0,startByteAdr
为开始地址,bitAdr
为位。
//读辅助继电器M(从MB0读一个字节)
byte[] result = plc.ReadBytes(DataType.Memory, 0, 0, 1);
//读输入I(IB0)
byte[] result = plc.ReadBytes(DataType.Input, 0, 0, 1);
//读输出Q(QB0)
byte[] result = plc.ReadBytes(DataType.Output, 0, 0, 1);
//写辅助继电器M(置位M0.1)
plc.WriteBit(DataType.Memory, 0, 0, 1, true);
//写辅助继电器M(复位M0.2)
plc.WriteBit(DataType.Memory, 0, 0, 2, false);
字符(串)读写
字符串读取比较麻烦,因为其长度是不定的,S7netplus好像也没提供一个便捷的读写方法。我自己瞎琢磨出了读写方法,不算复杂。
DB块存String类型时默认分配256个字节,使用时只占用一部分。以DB1偏移量0.0为例:
该数据的第一个字节DB1.DBB0
为16#FE
。第二个字节DB1.DBB1
存储的byte型数据为字符串长度n。从DB1.DBB2
开始的n个字节是字符串的内容。依次读取这n字节,获取的是十进制数据,转化为字符后连接成字符串即可。Char类型读取方法类似:
private string GetString(int db, int addr)
{
string addr1 = "DB" + db.ToString() + ".DBB" + (addr + 1).ToString();
int len = int.Parse(plc.Read(addr1).ToString());
string getstring = "";
for (int i = 0; i < len; i++)
{
string addr2 = "DB" + db.ToString() + ".DBB" + (addr + 2 + i).ToString();
string result = plc.Read(addr2).ToString();
int value = Convert.ToInt32(result, 10);
string stringValue = Char.ConvertFromUtf32(value);
getstring += stringValue;
}
return getstring;
}
Char写入就比较简单了,不需要转换,直接对地址写入string类型的单个字符即可。String需要先写入长度,再依次写入每一个字符。第一位的16#FE
至今不知含义,但不影响读写。String的拆分和写入如下:
private void PutString(int db, int addr, string data)
{
byte len = Convert.ToByte(data.Length);
string addr1 = "DB" + db.ToString() + ".DBB" + (addr + 1).ToString();
plc.Write(addr1, len);
char[] single = data.ToCharArray();
for (int i = 0; i < data.Length; i++)
{
string addr2 = "DB" + db.ToString() + ".DBB" + (addr + 2 + i).ToString();
plc.Write(addr2, single[i].ToString());
}
}
偷偷放一个自己做的调试小工具