倍福PLC——ADS上位机通讯
- 前言
- 一、ADS服务
- 二、使用ads函数进行数据通讯
- 1.通过句柄读写
- c#读取写入代码
前言
工程中涉及与倍福plc的交互用到ads通讯,在此稍作研究总结。
一、ADS服务
本机没有安装倍福全家桶的需要安装一下这个TwinCAT System。
安装完成后需要配置一下服务中的端口。(具体操作等下次有机会再记录把)
二、使用ads函数进行数据通讯
1.通过句柄读写
先看一下两端的数据配置如下:
PLC端:
结构体定义:
此处顶部的{attribute ‘pack_mode’:= ‘1’}指示了内存排列的方式,与下文c#端设置结构体排列时有着对应的关系,请着重关注一下
上位机C#端:
看一下两边类型对照表:
重点是结构体定义:
很多人在这个地方喜欢把Struct用Class定义,在单独传输结构体数据时是没有问题的,但是当要传输一个结构体数组,用Class定义则会引起代码报错,此处注意!
[StructLayout(LayoutKind.Sequential, Pack = 1)]public struct myStruct{public void StructIni() {b = false;bt = 0;word = 0;dword = 0;sint = 0;m_int = 0;dint = 0;lint = 0;real = 0;lreal = 0;string_en = "";intArr = new int[10];intArr2 = new int[9];}[MarshalAs(UnmanagedType.I1)]public bool b ;[MarshalAs(UnmanagedType.U1)]public byte bt ;[MarshalAs(UnmanagedType.U2)]public ushort word ;[MarshalAs(UnmanagedType.U4)]public uint dword ;[MarshalAs(UnmanagedType.I1)]public sbyte sint ;[MarshalAs(UnmanagedType.I2)]public short m_int;[MarshalAs(UnmanagedType.I4)]public int dint ;[MarshalAs(UnmanagedType.I8)]public long lint ;[MarshalAs(UnmanagedType.R4)]public float real;[MarshalAs(UnmanagedType.R8)]public double lreal ;[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]public string string_en; //plc端string长10[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]public int[] intArr ;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]public int[] intArr2 ; //对应plc端是个二维数组}
结构体数组:
[StructLayout(LayoutKind.Sequential, Pack = 1)]public struct myStructArr{[MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]public myStruct[] arr ;}
c#读取写入代码
定义类型与句柄结构方便操作:
public struct PLCPointInfo{public PLCType plcType;public int Handel;public int byteCount;}public enum PLCType{BOOL,BYTE,WORD,DWORD,SINT,INT,DINT,LINT,USINT,UINT,UDINT,ULINT,REAL,LREAL,STRING, //STRING_CH, //本来是用网上转UTF8格式的来处理,但是我已发现的bug太多,在此不推荐WSTRING, STRUCT}
首先需要获取对应数据的句柄:
TcAdsClient ads;private void Form1_Load(object sender, EventArgs e) //连接{ads = new TcAdsClient();ads.Connect("169.254.109.125.1.1", 851);}//存储数据结构的字典Dictionary<string, PLCPointInfo> dic = new Dictionary<string, PLCPointInfo>();private void button1_Click(object sender, EventArgs e) //获取数据句柄{dic.Clear();foreach (PLCType item in Enum.GetValues(typeof(PLCType))){dic.Add(item.ToString().ToLower(), new PLCPointInfo(){plcType = item,Handel = ads.CreateVariableHandle("MAIN." + item.ToString().ToLower() + "1")});}dic["string"] = new PLCPointInfo(){plcType = PLCType.STRING,Handel = ads.CreateVariableHandle("MAIN." + "string" + "1"),byteCount = 10};//dic["string_ch"] = new PLCPointInfo()//{// plcType = PLCType.STRING_CH,// Handel = ads.CreateVariableHandle("MAIN." + "string_ch" + "1"),// byteCount = 10//};dic["wstring"] = new PLCPointInfo(){plcType = PLCType.WSTRING,Handel = ads.CreateVariableHandle("MAIN." + "wstring" + "1"),byteCount = 10};}
读取PLC中的数据:
public object GetPLCValue(PLCPointInfo info, Type type = null){switch (info.plcType){case PLCType.BOOL:return (ads.ReadAny(info.Handel, typeof(bool)));case PLCType.BYTE:case PLCType.USINT:return (ads.ReadAny(info.Handel, typeof(byte)));case PLCType.WORD:case PLCType.UINT:return (ads.ReadAny(info.Handel, typeof(ushort)));case PLCType.DWORD:case PLCType.UDINT:return (ads.ReadAny(info.Handel, typeof(uint)));case PLCType.SINT:return (ads.ReadAny(info.Handel, typeof(sbyte)));case PLCType.INT:return (ads.ReadAny(info.Handel, typeof(short)));case PLCType.DINT:return (ads.ReadAny(info.Handel, typeof(int)));case PLCType.LINT:return (ads.ReadAny(info.Handel, typeof(long)));case PLCType.ULINT:return (ads.ReadAny(info.Handel, typeof(ulong)));case PLCType.REAL:return (ads.ReadAny(info.Handel, typeof(float)));case PLCType.LREAL:return (ads.ReadAny(info.Handel, typeof(double)));case PLCType.STRING:return (ads.ReadAny(info.Handel, typeof(string), new int[] { info.byteCount == 0 ? 255 : info.byteCount }));//case PLCType.STRING_CH: //不建议使用UTF8编码传输// object o = (ads.ReadAny(info.Handel, typeof(string), new int[] { info.byteCount == 0 ? 255 : info.byteCount+10 }));// byte[] byteArr = Encoding.Default.GetBytes(o.ToString());// return Encoding.UTF8.GetString(byteArr);case PLCType.WSTRING: //wstring类型直接这样读取就行,plc端wstring编码是UTF16,所有直接转换成Unicode就行object o2 = (ads.ReadAny(info.Handel, typeof(string), new int[] { info.byteCount == 0 ? 255 : info.byteCount }));byte[] byteArr2 = Encoding.Default.GetBytes(o2.ToString());return Encoding.Unicode.GetString(byteArr2);case PLCType.STRUCT:if (type == null){throw new Exception("输入结构体C#端类型为null!");}return (ads.ReadAny(info.Handel, type));default:return null;break;}}//数据结果存放的字典Dictionary<string, object> value = new Dictionary<string, object>();private void button2_Click(object sender, EventArgs e){value.Clear();foreach (var item in dic){if (item.Value.plcType == PLCType.STRUCT){value.Add(item.Key, GetPLCValue(item.Value, typeof(myStructArr)));}else{value.Add(item.Key, GetPLCValue(item.Value));}}}
结果:
数据写入PLC:
public void SetPLCValue(PLCPointInfo info,object value, Type type = null){object input;switch (info.plcType){case PLCType.BOOL:input = (bool)value;ads.WriteAny(info.Handel, input);break;case PLCType.BYTE:case PLCType.USINT:input = Convert.ToByte(value);ads.WriteAny(info.Handel, input);break;case PLCType.WORD:case PLCType.UINT:input = Convert.ToUInt16(value);ads.WriteAny(info.Handel, input);break;case PLCType.DWORD:case PLCType.UDINT:input = Convert.ToUInt32(value);ads.WriteAny(info.Handel, input);break;case PLCType.SINT:input = Convert.ToSByte(value);ads.WriteAny(info.Handel, input);break;case PLCType.INT:input = Convert.ToInt16(value);ads.WriteAny(info.Handel, input);break;case PLCType.DINT:input = Convert.ToInt32(value);ads.WriteAny(info.Handel, input);break;case PLCType.LINT:input = Convert.ToInt64(value);ads.WriteAny(info.Handel, input);break;case PLCType.ULINT:input = Convert.ToUInt64(value);ads.WriteAny(info.Handel, input);break;case PLCType.REAL:input = Convert.ToSingle(value);ads.WriteAny(info.Handel, input);break;case PLCType.LREAL:input = Convert.ToDouble(value);ads.WriteAny(info.Handel, input);break;case PLCType.STRING:ads.WriteAnyString(info.Handel, value.ToString(), info.byteCount == 0? value.ToString().Length : info.byteCount, Encoding.Default);break;//case PLCType.STRING_CH: //用UTF8以WriteAny发送过去的数据会存在最后一个字符乱码的情况(最后一个编码被改成63空格结束符)用stream流发送,不会乱码,但是在此读取时又会出现奇怪的乱码问题,原因以后有时间再研究一下//更新一下,在通过这种往内存里写东西的时候,比如string(10)我写入(“嗡嗡嗡”)后再次写入(“哇哇”)则会呈现(“哇哇嗡”)的情况//也就是写入内存小的,没用到的内存不会被清除,读取的时候就会有问题//我在写入时新增结束符或把其他内存都付0也不行//在读取时最后一位莫名其妙变成了63导致最后一个字符乱码,由于找不到倍福数据传输的结构,实在找不到原因了。若以后有新发现,会继续更新// byte[] byteArr = Encoding.UTF8.GetBytes(value.ToString());// byte[] newbyteArr;// newbyteArr = new byte[byteArr.Length+1];// byteArr.CopyTo(newbyteArr, 0);// for (int i = byteArr.Length; i < byteArr.Length + 1; i++)// {// newbyteArr[i] = Convert.ToByte('\0');// }// //if (byteArr.Length < info.byteCount)// //{// // newbyteArr = new byte[info.byteCount];// // byteArr.CopyTo(newbyteArr, 0);// // for (int i = byteArr.Length; i < newbyteArr.Length; i++)// // {// // newbyteArr[i] = Convert.ToByte('\0');// // }// //}// //else if (byteArr.Length > info.byteCount)// //{// // throw new Exception("输入数据长度超过设定长度!");// //}// //else// //{// // newbyteArr = byteArr;// //}// //ads.WriteAnyString(info.Handel, Encoding.Default.GetString(newbyteArr), info.byteCount == 0 ? value.ToString().Length : info.byteCount, Encoding.Default);// using (AdsStream rStream = new AdsStream(newbyteArr.Length))// {// rStream.Seek(0, System.IO.SeekOrigin.Begin);// using (AdsBinaryWriter writer = new AdsBinaryWriter(rStream))// {// writer.Write(byteArr);// ads.Write(info.Handel, rStream, 0, newbyteArr.Length);// }// }// break;case PLCType.WSTRING: //读取时选定Encoding.Unicode就行(不支持UTF8编码读取)ads.WriteAnyString(info.Handel, value.ToString(), info.byteCount == 0 ? value.ToString().Length : info.byteCount, Encoding.Unicode);break;case PLCType.STRUCT:if (type == null){throw new Exception("输入结构体C#端类型为null!");}ads.WriteAny(info.Handel, value);break;default:input = null;break;}}private void button3_Click(object sender, EventArgs e) //写入数据{foreach (var item in dic){switch (item.Value.plcType){case PLCType.BOOL:SetPLCValue(item.Value, true);break;case PLCType.BYTE:SetPLCValue(item.Value, 100);break;case PLCType.WORD:SetPLCValue(item.Value, 300);break;case PLCType.DWORD:SetPLCValue(item.Value, 2000);break;case PLCType.SINT:SetPLCValue(item.Value, -100);break;case PLCType.INT:SetPLCValue(item.Value, -2000);break;case PLCType.DINT:SetPLCValue(item.Value, -20000);break;case PLCType.LINT:SetPLCValue(item.Value, -2000000);break;case PLCType.USINT:SetPLCValue(item.Value, 200);break;case PLCType.UINT:SetPLCValue(item.Value, 20000);break;case PLCType.UDINT:SetPLCValue(item.Value, 200000);break;case PLCType.ULINT:SetPLCValue(item.Value, 200000);break;case PLCType.REAL:SetPLCValue(item.Value, 2.23);break;case PLCType.LREAL:SetPLCValue(item.Value, -32.15);break;case PLCType.STRING:SetPLCValue(item.Value, "zxcvb");break;//case PLCType.STRING_CH:// SetPLCValue(item.Value, "我的天哪");// break;case PLCType.WSTRING:SetPLCValue(item.Value, "哇撒");break;case PLCType.STRUCT:myStructArr mstruct = new myStructArr(){arr = new myStruct[9]};for (int i = 0; i < mstruct.arr.Length; i++){mstruct.arr[i].StructIni();mstruct.arr[i].dint = i+1;mstruct.arr[i].intArr[0] = i + 1;mstruct.arr[i].string_en = (i+1).ToString();}SetPLCValue(item.Value, mstruct,typeof(myStructArr));break;default:break;}}}
写入结果:
结构体数组: