测试做完很久了,一直拖到现在才更新,给我自己一个大嘴巴。
通过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设定一致
源代码如下:
///
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
///
public void Open()
源代码如下:
///
/// Close connection to PLC
///
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";
}
}
代码如下:
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)时也是个不小的麻烦。希望本文可以帮助到大伙。