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;
C#之后就可以用了。
连接
S7netplus支持的CPU包括:LOGO!0BA8,S7-200,S7-300,S7-400,S7-1200,S7-1500。(后来SMART支持PN通讯之后也可以连SMART了,更新了可能有好一阵子了,我最近才发现_2022.3.11)
plc连接的定义为:
public Plc(CpuType cpu, string ip, short rack, short slot);
C#四个参数分别是: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 = "连接已断开";
}
}
}
}
C#文档中的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 = "数据已写入";
}
}
}
C#布尔读写
S7netplus并没有直接提供单个位的查询。但是提供了字节查询,并附带了直接从字节提取位的方法:
byte[] myByte = plc.ReadBytes(...);
byte myByte[0] = 5; // 0000 0101
myByte.SelectBit(0) // true
myByte.SelectBit(1) // false
C#读写的定义如下:
public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count);
public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value);
C#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);
C#字符(串)读写
字符串读取比较麻烦,因为其长度是不定的,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;
}
C#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());
}
}
C#附:含中文字符串处理
此方法由 @小白 提供。
据 @小白 考证,数据第一字节的含义为整个字符串的长度,参考来源见此。读取含中文字符串需要引入using System.Web;
,直接输入using
指令会无效,需要在项目资源管理器引用System.Web,读取方法如下:
private string GetStringGBK(Plc plc, int db, int addr)
{
//测试字符串为"测试abc123测试@#"
string getstring = string.Empty;
string addr1 = "DB" + db.ToString() + ".DBB" + (addr + 1).ToString();
int len = int.Parse(plc.Read(addr1).ToString());
var bytes = plc.ReadBytes(DataType.DataBlock, db, addr + 2, len);
int i = 0;
while (i < len)
{
if (bytes[i] > 127)
{
byte[] b = new byte[] { bytes[i], bytes[i + 1] };
//当字符小于127位时,与ASCII字符相同,但当两个大于127位的字符连接在一起时,表示一个汉字
string result = HttpUtility.UrlEncode(b);
string a = HttpUtility.UrlDecode(result, Encoding.GetEncoding("GB2312"));
getstring += a;
i += 2;
}
else
{
//除中文外字符为单个字节,且其编码与ASCII,相同,直接用ASCII转换就可以正确读取字符了。
byte[] b = new byte[] { bytes[i] };
getstring += Encoding.ASCII.GetString(b);
i++;
}
}
return getstring;
}
C#写入含中文字符串:
private void PutStringGBK(Plc plc, int db, int addr, string data)
{
string addr1 = "DB" + db.ToString() + ".DBB" + (addr + 1).ToString();
byte[] gbk = Encoding.GetEncoding("GBK").GetBytes(data);
byte len2 = Convert.ToByte(gbk.Length);
plc.Write(addr1, len2);
plc.WriteBytes(DataType.DataBlock, db, addr+2, gbk);
}
C#
小白说:
这个测试读取我写入的中文字符串是没办法正确读取的,原因是
@小白s7.net
的源码为return System.Text.Encoding.ASCII.GetString(bytes);
他用了ASCII解码,而GB2312是基于ASCII上的(算是拓展吧),那就很奇怪为什么,在PLC直接输入中文字符,能用这个方法读取了。。。。,不过为了弥补读取我的写入方法的读取方法(感觉有点绕口哈哈),方法也提供在了评论区了哦。希望大家共同进步
再次感谢小白,呱唧呱唧
大佬 配享太庙!
有没有群 学习一下
太过誉了,不敢当不敢当没有群哦,只是偶尔在博客记录一点心得而已
博主有遇到过使用s7netplus向1200plc写入长字符串时没有抛异常但实际没写进去的情况吗
这…我没有写过长的字符串,所以没有遇到过
好爱爱爱啊啊
感谢博主分享,照着博主给的 UI 和代码,我照猫画虎撸了一遍代码。
评论区提到的 Real 类型读写不准确,大家把 double 改成 float 就好了。
大佬,调试工具有空麻烦发一份,谢谢!
调试工具麻烦发一份,多谢。
Real类型数据写入有问题,不仅Real数值不对,还会改变Dint数据的值。可能是写入的存储地址是反着的吗?
real型的写入有问题啊
当时测试时记得没什么问题,如有疏漏还请指正
大佬可以发一份代码文件和测试工具吗?
不好意思,代码文件暂不共享,关键代码基本都在文章中,可参考。测试工具已发。
大佬可否发送一份给我哇,上面的下载不了。谢谢了!
已发
大佬可否发我一份,谢谢大佬yyds.
已发
S7netplus有具体的详细文档吗
有文档,当初似乎在Github上找到的。现在手头没有。
可不可以大佬给我发一份工具,让我试试呀~
已发
感谢大佬的整理和署名,,对c#的转码处理还是不太熟悉,感觉有点臃肿,希望大佬优化哟
已经把读写方法放入文章了哦,感谢小白提供的思路和方法,大家共同进步
读取写入的字符串方法来了,下午在给业主培训(ó﹏ò。)。下面是读取用写入字符串的方法写入的字符的。
private string GetStringTest(Plc plc, int db, int addr)//测试字符为"测试abc123测试@#"
{
string getstring = "";
string addr1 = "DB" + db.ToString() + ".DBB" + (addr + 1).ToString();
int len = int.Parse(plc.Read(addr1).ToString());
var bytes = plc.ReadBytes(DataType.DataBlock, db, addr+ 2, len);
int i = 0;
while (i < len)
{
if (bytes[i] > 127)
{
byte[] b = new byte[] { bytes[i], bytes[i + 1] };
//string result= Encoding.GetEncoding(" GB2312 ").GetString(b);
//当字符小于127位时,与ASCII字符相同,但当两个大于127位的字符连接在一起时,表示一个汉字
string result = HttpUtility.UrlEncode(b);
string a = System.Web.HttpUtility.UrlDecode(result, System.Text.Encoding.GetEncoding("GB2312"));
getstring = getstring + a;
i = i + 2;
}
else
{
//除中文外字符为单个字节,且其编码与ASCII,相同,直接用ASCII转换就可以正确读取字符了。
byte[] b = new byte[] { bytes[i] };
getstring = getstring + System.Text.Encoding.ASCII.GetString(b);
i = i+1;
}
}
return getstring;
}
/这是plc写入字符串含中文的方法,还是等待大佬的优化/
private void PutStringTest(Plc plc, int db, int addr, string data)
{
//byte len = Convert.ToByte(data.Length+2);// string str3(data) = "测试";
string addr1 = "DB" + db.ToString() + ".DBB" + (addr+1).ToString();
byte[] gbk = Encoding.GetEncoding("GBK").GetBytes(data);//测试时传入的data为"测试abc123"
byte len2 = Convert.ToByte(gbk.Length);
//将PLC 数据块中string 数据类型的第二个字节写入字符串的有效长度。
plc.Write(addr1, len2);
//将字符串数据转换为GBK编码的数组,写入到PLC。
plc.WriteBytes(DataType.DataBlock, db, addr+2, gbk);
}
下次再把读取的贴出来把。
好厉害,下午我整理一下放到文章里,会署名的哦
求大佬发下文件
已发
dalao,读写字符串的时候,如果是中文的话,是不能正确的读取写入
这个我还真没试过,到现在也没遇到需要读写中文的情况,文档里也没有相关说明。如果试了不行的话说明就真的不支持了…
好滴吧,我看了一下其他的字符占一个字节,我再试一下,中文字符的时候写两个字符试一下把,对了,第一个字节代表的含义是整个字符串的长度,这是原文连接
https://www.ad.siemens.com.cn/productportal/Prods/S7-1200_PLC_EASY_PLUS/07-Program/02-basic/01-Data_Type/09-String.html
看来是可以用中文的,不过看来需要处理长度,编码这些麻烦的事。学到了
涉及到转码就感觉很复杂了呀
今天测试了一下还是可以写入中文的,
byte len = Convert.ToByte(data.Length+2);// string str3(data) = "测试";byte[] gbk = Encoding.GetEncoding("GBK").GetBytes(data);plc.WriteBytes(DataType.DataBlock,1050,2,gbk);
哇,这么快就解决了。感谢你的经验
string str2 = plc.Read(DataType.DataBlock, 1050,68,VarType.String, 1).ToString();
这个可以正常读取中文,读取后根据西门子的string结构进行截取就可以了,就是写入的有点难度呀这个测试读取我写入的中文字符串是没办法正确读取的,原因是s7.net 的源码为
return System.Text.Encoding.ASCII.GetString(bytes);
他用了ASCII解码,而GB2312是基于ASCII上的(算是拓展吧),那就很奇怪为什么,在PLC直接输入中文字符,能用这个方法读取了。。。。,不过为了弥补读取我的写入方法的读取方法(感觉有点绕口哈哈),方法也提供在了评论区了哦。希望大家共同进步哭了, “西门子PLC简易调试工具v2.0.0” 下不了了
本站的下载服务停止了哦,文件已发送到你留言的邮箱
收到,感谢
Up有试过直接读取整个块吗
想要源码,博主能发一下不
23333333
博主大大,这个例程的源码方便分享学习一下咩,求求了
这个博主↑已经一个月没有更新了怎么办,急,在线等
这个博主出差很久了还是不能回家怎么办,也急,也在线等
不懂代码,看到头就晕了。
对喜欢的人来说就不是问题了
看不懂的dalao就完事儿了
岂敢岂敢,略懂皮毛
还是嵌入式开发大佬
说吧,你还有多少东西藏着掖着
没有没有,不是大佬。大学的时候正经课没好好上,乱七八糟的东西学了一大堆
dalao口中的学了一大堆乱七八糟:C、C#、嵌入式、网站开发、JS/CSS、美食、画画…(此处省略n)
我口中的学了一大堆乱七八糟:sudo rm -rf /*
哈哈哈哈,还是你厉害,我发的所有东西你都知道,你发的我却什么也看不懂
没错!没错就是这样~ 我拼的了PLC这三个英文字母,也知道namespace是C#的是不是很腻害!!!
哇,好腻害好腻害,啪叽啪叽٩(ˊᗜˋ*)و