利用S7netplus通讯西门子S7系列PLC


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.DBB016#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直接输入中文字符,能用这个方法读取了。。。。,不过为了弥补读取我的写入方法的读取方法(感觉有点绕口哈哈),方法也提供在了评论区了哦。希望大家共同进步

@小白

再次感谢小白,呱唧呱唧

Powered by WordPress. Theme by Alx.