赞
踩
测试做完很久了,一直拖到现在才更新,给我自己一个大嘴巴。
通过C#读写西门子对应存储区的数据。
支持PLC中大部分的常用类型:Bit, Byte, Word,DWord, Int,DInt,Real, LReal,String, WString:
要是有时间更新了,我再更新到这里。
源码下载地址:GitHub地址有兴趣的小伙伴可以详细研究一下,在此只对用到的函数做简要介绍。
源代码如下:
public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot): this(cpu, ip, DefaultPort, rack, slot)
//cpu:plc的类型,看下面代码
//ip:指示连接plc的网口地址
//rack:指示plc机架号
//slot:指示plc机槽号
public enum CpuType
{
S7200 = 0,
Logo0BA8 = 1,
S7200Smart = 2,
S7300 = 10,
S7400 = 20,
S71200 = 30,
S71500 = 40,
}
关于rack(机架)与slot(机槽)的设定要和plc设定一致
源代码如下:
/// <summary>
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
/// </summary>
public void Open()
源代码如下:
/// <summary>
/// Close connection to PLC
/// </summary>
public void Close()
源代码如下:
public object? Read(string variable)
//variable:plc中的数据地址表示,如:"DB1.DBX0.0", "DB20.DBD200", "MB20", "T45"等
源代码如下:
public object? Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0)
//dtatType:plc内部对应的存储区域类型
//db:指示地址号,如"DB1.DBX0.0"此处就为1
//startByteAdr: 开始读取的位置。如:”DB10.DBW8“此处为8
//varCount:连续读取当前类型的个数(string类型特例)
public enum DataType
{
Input = 129,
Output = 130,
Memory = 131,
DataBlock = 132,
Timer = 29,
Counter = 28
}
源代码如下:
public void Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1)
//dtatType:plc内部对应的存储区域类型
//db:指示地址号,如"DB1.DBX0.0"此处就为1
//startByteAdr: 开始读取的位置。如:”DB10.DBW8“此处为8
//value:写入plc的数据(内部会根据它原来的c#类型去自动分解成字符数组)
没有实际plc的可以通过仿真测试,具体配置可以转到我的另外两篇文章:
西门子——博图V16与PLCSIM Advanced仿真通讯配置(1500系列)
西门子——好用的通讯仿真通讯工具NetToPLCsim
代码如下:
private void btn_Connect_Click(object sender, EventArgs e) { if (plc == null) { plc = new Plc((CpuType)Enum.Parse(typeof(CpuType), cmbbx_plcType.Text), tb_IP.Text, Convert.ToInt16(tb_rack.Text), Convert.ToInt16(tb_slot.Text)); } if (!plc.IsConnected) { plc.Open(); btn_Connect.Text = "断开"; textBox2.Text = textBox2.Text + "打开成功!\r\n"; } else { plc.Close(); btn_Connect.Text = "链接"; textBox2.Text = textBox2.Text + "连接关闭!\r\n"; } }
我配置的plc机架与机槽:
代码如下:
struct plcInfo { public string DataPath; public VarType DataType; public int ByteCount; } //绑定数据的字典 Dictionary<String, plcInfo> dic = new Dictionary<string, plcInfo>(); private void Form1_Load(object sender, EventArgs e) { string[] cpuTypeArr = Enum.GetNames(typeof(CpuType)); foreach (var item in cpuTypeArr) { cmbbx_plcType.Items.Add(item); } cmbbx_plcType.SelectedItem = CpuType.S71500.ToString(); string[] dataTypeArr = Enum.GetNames(typeof(VarType)); foreach (var item in dataTypeArr) { cmbbx_dataType.Items.Add(item); } cmbbx_dataType.SelectedItem = VarType.Int.ToString(); dic.Add("bit", new plcInfo() { DataPath ="DB10.DBX0.0",DataType = VarType.Bit } ); dic.Add("byte", new plcInfo() { DataPath = "DB10.DBB1", DataType = VarType.Byte }); dic.Add("word", new plcInfo() { DataPath = "DB10.DBW2", DataType = VarType.Word } ); dic.Add("dword", new plcInfo() { DataPath = "DB10.DBD4", DataType = VarType.DWord }); dic.Add("int", new plcInfo() { DataPath = "DB10.DBW8", DataType = VarType.Int }); dic.Add("dint", new plcInfo() { DataPath = "DB10.DBD10", DataType = VarType.DInt }); dic.Add("real", new plcInfo() { DataPath = "DB10.DBD14", DataType = VarType.Real }); dic.Add("lreal", new plcInfo() { DataPath = "DB10.DBX18.0", DataType = VarType.LReal } ); dic.Add("string", new plcInfo() { DataPath = "DB10.DBX26.0", DataType = VarType.String } ); dic.Add("s7wstring", new plcInfo() { DataPath = "DB10.DBX282.0", DataType = VarType.S7WString }); //dic.Add("s5time", new plcInfo() { DataPath = "DB10.DBW794", DataType = VarType.S5Time }); dic.Add("s5time", new plcInfo() { DataPath = "DB10.DBD794", DataType = VarType.S5Time }); }
private object GetValue(plcInfo plcInfo) { if (plc != null && plc.IsConnected) { var plcData = new PLCAddress(plcInfo.DataPath); VarType useType = plcInfo.DataType; switch (useType) { case VarType.Bit: return plc.Read(plcInfo.DataPath); break; case VarType.Byte: return plc.Read(plcInfo.DataPath); break; case VarType.Word: return plc.Read(plcInfo.DataPath); break; case VarType.DWord: return plc.Read(plcInfo.DataPath); break; case VarType.Int: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.Int, 1); break; case VarType.DInt: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.DInt, 1); break; case VarType.Real: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.Real, 1); break; case VarType.LReal: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.LReal, 1); break; case VarType.String: byte count = (byte)plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte + 1, VarType.Byte, 1); return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte + 2, VarType.String, count); break; case VarType.S7String: byte S7StringCount = (byte)plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte + 1, VarType.Byte, 1); return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.S7String, S7StringCount); break; case VarType.S7WString: short S7WStringCount = ((short)plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte + 2, VarType.Int, 1)); return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.S7WString, S7WStringCount); break; case VarType.S5Time: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.S5Time, 1); break; case VarType.Counter: break; case VarType.DateTime: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.DateTime, 1); break; case VarType.DateTimeLong: return plc.Read(plcData.DataType, plcData.DbNumber, plcData.StartByte, VarType.DateTimeLong, 1); break; default: throw new Exception("无输入"); break; } return null; } else { return null; } } private void btn_Read_Click(object sender, EventArgs e) { foreach (var item in dic) { string showStr; showStr = GetValue(item.Value).ToString(); textBox2.Text = textBox2.Text + item.Value.DataPath + "内容:" + showStr + "\r\n"; } }
此处需要说明为什么Bit,Byte,Word与Dword可以直接用地址读取不会出错呢?
(可以尝试看一下int等其他数据用Read(plcInfo.DataPath)会出现什么情况)
1)首先地址样式必须与类型对应如DBX对应bit、DBB对应byte、DBW对应Word、DBD对应Dword;
2)这四个类型存储方式都特别简单,而且数据没有正负之分。
还有为何读取String与Wstring操作比较复杂(先看我另一篇文章了解西门子中两种数据的结构)
1)数据结构的特殊;
2)字符串类型实际是字符数组,所以在Read(dataType, db,startByteAdr, varType, varCount)中,varCount要特别注意是字符串字符的个数(Wstring是字符个数 * 2);
最后是关于开源代码作者的S7String类型,读取的数据其实和string类型一样,只是内部自动给数据做了处理
private void SetValue(plcInfo plcInfo,object value) { if (plc != null && plc.IsConnected) { var plcData = new PLCAddress(plcInfo.DataPath); VarType useType = plcInfo.DataType; switch (useType) { case VarType.Bit: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte,Convert.ToBoolean(value)); break; case VarType.Byte: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, Convert.ToByte( value)); break; case VarType.Word: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, Convert.ToUInt16( value)); break; case VarType.DWord: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, Convert.ToUInt32( value)); break; case VarType.Int: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, Convert.ToInt16( value)); break; case VarType.DInt: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, Convert.ToInt32( value)); break; case VarType.Real: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, Convert.ToSingle( value)); break; case VarType.LReal: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte,Convert.ToDouble( value)); break; case VarType.String: byte Count = Convert.ToByte(plcInfo.ByteCount == 0 ? 255 : plcInfo.ByteCount); byte UsingCount = Convert.ToByte( value.ToString().Length); byte[] stringArr = new byte[value.ToString().Length + 2]; stringArr[0] = Count; stringArr[1] = UsingCount; byte[] OstringArr = Encoding.ASCII.GetBytes(value.ToString()); for (int i = 0; i < OstringArr.Length; i++) { stringArr[2 + i] = OstringArr[i]; } plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, stringArr); break; case VarType.S7String: plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, value.ToString()); break; case VarType.S7WString: short wsCount = Convert.ToInt16(plcInfo.ByteCount == 0 ? 255 : plcInfo.ByteCount); short wsUsingCount = Convert.ToInt16(value.ToString().Length); byte[] wsstringArr = new byte[value.ToString().Length * 2 + 4]; wsstringArr[0] = Convert.ToByte(wsCount >> 8); wsstringArr[1] = Convert.ToByte(wsCount & 0x00FF); wsstringArr[2] = Convert.ToByte(wsUsingCount >> 8); wsstringArr[3] = Convert.ToByte(wsUsingCount & 0x00FF); byte[] wsOstringArr = Encoding.BigEndianUnicode.GetBytes(value.ToString()); for (int i = 0; i < wsOstringArr.Length; i++) { wsstringArr[4 + i] = wsOstringArr[i]; } plc.Write(plcData.DataType, plcData.DbNumber, plcData.StartByte, wsstringArr); break; default: break; } } } private void btn_Write_Click(object sender, EventArgs e) { object obj = new object(); foreach (var item in dic) { switch (item.Value.DataType) { case VarType.Bit: obj = false; SetValue(item.Value,obj ); break; case VarType.Byte: obj = 12; SetValue(item.Value, obj); break; case VarType.Word: obj = 100; SetValue(item.Value, obj); break; case VarType.DWord: obj = 100; SetValue(item.Value, obj); break; case VarType.Int: obj = 100; SetValue(item.Value, obj); break; case VarType.DInt: obj = 100; SetValue(item.Value, obj); break; case VarType.Real: obj = 10.22f; SetValue(item.Value, obj); break; case VarType.LReal: obj = 20.22; SetValue(item.Value, obj); break; case VarType.String: obj = "qwer"; SetValue(item.Value, obj); break; //case VarType.S7String: // break; case VarType.S7WString: obj = "嗡嗡嗡"; SetValue(item.Value, obj); break; case VarType.S5Time: obj = 10; SetValue(item.Value, obj); break; default: break; } textBox2.Text = textBox2.Text + item.Value.DataPath + "写入:" + obj.ToString() + "\r\n"; } }
此处需要注意的依旧的string与wstring这两个类型同样的先看(西门子——不同数据的存储方式),传输字符数组需要自己组合
再一个西门子PLC是大端模式(可看Unicode字符编码),需要注意传输Wstring时高低位的排序
到此与西门子PLC的通讯测试就结束了,我只测试了西门子1500系列,若大家在其他系列测试有问题,可以留言我后台联系我。本文只研究了一些常用类型,基本都够用了,还有一些批量读取Struct(整个db块)的,我觉得没必要而且里面含有字符串(string和wstring)时也是个不小的麻烦。希望本文可以帮助到大伙。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。