TC3与C#ADS通讯进行数据读写
本文章旨在说明TwinCAT3的ADS通讯应用,利用C#对倍福中常见数据类型(INT、BOOL、STRING、WSTRING、ARRAY、STRUCT等)变量进行读写操作。
1. TwinCAT ADS技术
(备注:此部分大家可以前往倍福虚拟学堂进行学习: https://tr.beckhoff.com.cn/course/view.php?id=150)
- ADS即(Automation Device Specification)自动化设备规范;
- TwinCAT系统各模块均作为独立的设备;
- 每个任务均存在一个服务模块,服务端或客户端;
- 由Message Router统一交换数据。
2. ADS通讯方式及命令参数
2.1 ADS通讯方式
- 异步通讯变量上限:550
- 批量读取变量上限:500
- 异步和批量读取需要通道支持
2.2 ADS命令参数
3. 程序实现
本例中采用按变量名方式进行读写调试。其具体实现方法如下:
step1 建立工程
建立c#的项目工程、Twincat3 项目工程,在c#中实现对Twincat3 中的变量进行读写。
step2 添加引用
在C#工程中引入 TwinCAT.Ads.dll 库文件(此文件可到BECKHOFF官网下载)
step3 前期工作
1)TC3变量创建
TYPE StructAxis :
STRUCT
Triggle: BOOL:=TRUE;
Velocity: LREAL:=200;
//Acceleration: LREAL;
//Jrek: LREAL;
//Deceleration: LREAL;
//Done: BOOL;
//Busy: BOOL;
//Error: BOOL;
//ErrorID: LREAL;
END_STRUCT
END_TYPE
PROGRAM MAIN
VAR
//c# test
Date_Int: INT:=10;
Date_Bool: BOOL:=FALSE;
Date_Array: ARRAY[0..2] OF INT;
Date_Struct: StructAxis;
Date_string: STRING:='Made by dashuaixiaoping';
{attribute 'TcEncoding':='UTF-8'}
Date_wstring: STRING:=wsLiteral_TO_UTF8("大帅小平制作");
END_VAR
2)设计Form
本例添加以下控件进行调试:(比较粗糙,请忽略)
3)创建Adsclient
public TcAdsClient _client = new TcAdsClient();
4)连接PLC
_client.Connect(851);
5)创建句柄
public int _myIntHand = 0;
public int _myBoolHand = 0;
public int _myStringHand = 0;
public int _myWstringHand = 0;
public int _myArrayHand = 0;
public int _myStructHand = 0;
_myIntHand = _client.CreateVariableHandle("MAIN.Date_Int");
_myBoolHand = _client.CreateVariableHandle("MAIN.Date_Bool");
_myStringHand = _client.CreateVariableHandle("Main.Date_string");
_myWstringHand = _client.CreateVariableHandle("Main.Date_wstring");
_myArrayHand = _client.CreateVariableHandle("Main.Date_Array");
_myStructHand = _client.CreateVariableHandle("Main.Date_Struct");
6)释放句柄
//释放句柄
_client.DeleteVariableHandle(_myIntHand);
_client.DeleteVariableHandle(_myBoolHand);
_client.DeleteVariableHandle(_myStringHand);
_client.DeleteVariableHandle(_myWstringHand);
_client.DeleteVariableHandle(_myArrayHand);
_client.DeleteVariableHandle(_myStructHand);
step4 开始读写
TwinCAT.Ads.dll 库文件中提供了多个重载读写方法,本例中主要运用演示了:
public object ReadAny(int variableHandle, Type type, int[] args);
public int Read(int variableHandle, AdsStream dataStream, int offset, int length);
public void WriteAny(int variableHandle, object value, int[] args);
public void Write(int variableHandle, AdsStream dataStream, int offset, int length);
1)ReadAny/WriteAny 实现
//读
//INT
rInttb.Text = _client.ReadAny(_myIntHand, typeof(short)).ToString();
//BOOL
rBooltb.Text = _client.ReadAny(_myBoolHand, typeof(bool)).ToString();
//STRING
rStrtb.Text = _client.ReadAny(_myStringHand, typeof(string), new int[] { 16 }).ToString();
//WSTRING
Encoding defaultEncoding = Encoding.Default; //当前系统默认用"BGK"进行解码与编码
Encoding utf8Encoding = Encoding.UTF8;
string temStr = _client.ReadAny(_myWstringHand, typeof(string), new int[] { 32 }).ToString();
byte[] tempBytes = defaultEncoding.GetBytes(temStr); //解码:将字符、符号等转换为二进制机器语言
string str = utf8Encoding.GetString(tempBytes); //编码:将二进制机器语言转换为字符、符号等
rWstrtb.Text = str;
//ARRAY
short[] tempArr = (short[])_client.ReadAny(_myArrayHand, typeof(short[]), new int[] { 4 });
rArr0tb.Text = tempArr[0].ToString();
rArr1tb.Text = tempArr[1].ToString();
rArr2tb.Text = tempArr[2].ToString();
//STRUCT
Mystruct struct1 = (Mystruct)_client.ReadAny(_myStructHand, typeof(Mystruct));
rStruct1tb.Text = struct1.Triggle.ToString();
rStruct2tb.Text = struct1.Velocity.ToString();
//写
//INT
_client.WriteAny(_myIntHand, Convert.ToInt16(wInttb.Text));
//BOOL
_client.WriteAny(_myBoolHand, Convert.ToBoolean(wBooltb.Text));
//STRING
_client.WriteAny(_myStringHand, wStrtb.Text, new int[] { 16 });
//WSTRING
string str = wWstrtb.Text;
byte[] tempBytes = Encoding.UTF8.GetBytes(str);
_client.WriteAny(_myWstringHand,tempBytes);
//ARRAY
short[] temArr = new short[3];
temArr[0] = short.Parse(wArr0tb.Text);
temArr[1] = short.Parse(wArr1tb.Text);
temArr[2] = short.Parse(wArr2tb.Text);
_client.WriteAny(_myArrayHand, temArr);
//STRUCT
Mystruct tempStruct = new Mystruct();
tempStruct.Triggle = Boolean.Parse(wStruct1tb.Text);
tempStruct.Velocity = double.Parse(wStruct2tb.Text);
_client.WriteAny(_myStructHand, tempStruct);
2)Read/Write 实现
这里用到stream类,其具体含义与用法可参考博文:C# 温故而知新:Stream篇(—)。
//读
AdsStream rStream = new AdsStream(100);
AdsBinaryReader reader = new AdsBinaryReader(rStream);
//INT
_client.Read(_myIntHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin); //初始化流的当前位置( 等效于stream.Position = 0;)
rInttb.Text = reader.ReadInt16().ToString(); //读取两位数,同时position后移两位(ReadInt16的作用)
//BOOL
_client.Read(_myBoolHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin);
rBooltb.Text = reader.ReadBoolean().ToString();
//ARRAY
_client.Read(_myArrayHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin);
rArr0tb.Text = reader.ReadInt16().ToString();
rArr1tb.Text = reader.ReadInt16().ToString();
rArr2tb.Text = reader.ReadInt16().ToString();
//STRUCT
_client.Read(_myStructHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin);
rStruct1tb.Text = reader.ReadBoolean().ToString();
//因为struct中每个成员的数据类型不一样,
//系统默认将读到的每个成员以成员中长度最长的位数存储在stream中
//如此处的结构体中成员长度最长的数据类型为Double(对于倍福中的LREAL),占8位
//所以需将stream向后移动8位
rStream.Seek(8, System.IO.SeekOrigin.Begin);
rStruct2tb.Text = reader.ReadDouble().ToString();
//STRING
_client.Read(_myStringHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin);
rStrtb.Text = reader.ReadPlcString(81, Encoding.Default);
//WSTRING
_client.Read(_myWstringHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin);
//在倍福那边将WSTRING定义成UTF8格式的STRING,C#中以byte[]形式读取,然后以UTF8编码
rWstrtb.Text = Encoding.UTF8.GetString(reader.ReadBytes(81));
//写
AdsStream wStream = new AdsStream(100);
AdsBinaryWriter writer = new AdsBinaryWriter(wStream);
//INT
wStream.Seek(0, System.IO.SeekOrigin.Begin);
writer.Write(short.Parse(wInttb.Text));
_client.Write(_myIntHand, wStream, 0, 2);
//BOOL
wStream.Seek(0, System.IO.SeekOrigin.Begin);
writer.Write(Boolean.Parse(wBooltb.Text));
_client.Write(_myBoolHand, wStream, 0, 1);
//ARRAY
wStream.Seek(0, System.IO.SeekOrigin.Begin);
writer.Write(Int16.Parse(wArr0tb.Text));
writer.Write(Int16.Parse(wArr1tb.Text));
writer.Write(Int16.Parse(wArr2tb.Text));
_client.Write(_myArrayHand, wStream, 0, 6);
//STRUCT
wStream.Seek(0, System.IO.SeekOrigin.Begin);
writer.Write(Boolean.Parse(wStruct1tb.Text));
byte[] tempBytes = new byte[] { 0, 0, 0, 0, 0, 0, 0 }; //占位,使得BOOL和Double都占位8个
writer.Write(tempBytes);
writer.Write(Double.Parse(wStruct2tb.Text));
_client.Write(_myStructHand, wStream, 0, 16);
//STRING
wStream.Seek(0, System.IO.SeekOrigin.Begin);
byte[] tempBytes1 = Encoding.Default.GetBytes(wStrtb.Text);
writer.Write(tempBytes1);
_client.Write(_myStringHand, wStream, 0, 81);
//WSTRING
wStream.Seek(0, System.IO.SeekOrigin.Begin);
byte[] tempBytes2 = Encoding.UTF8.GetBytes(wWstrtb.Text);
writer.Write(tempBytes2);
_client.Write(_myWstringHand, wStream, 0, 81);
4. 注意事项
-
WSTRING的读写
WSTRING是TC3中的变量,主要用于存放中文字符,其详细介绍请参考博文:
BECKHOFF TwinCAT3 常见问题2值得注意的是,如果在C#中直接将WSTING通过string字符串的形式进行读写,会存在乱码问题,此问题的具体解决方案如下:
step1:
在TC3中将带中文的字符的变量命名为UTF-8编码规则下的STRING型;如:
{attribute 'TcEncoding':='UTF-8'}
Date_wstring: STRING:=wsLiteral_TO_UTF8("大帅小平制作");
{attribute ‘TcEncoding’:=‘UTF-8’} 的用法参照:https://infosys.beckhoff.com/english.php?content=…/content/1033/tc3_plc_intro/5873680907.html&id=6249601214176997827
如需深入了解Unicode/UTF-8/UTF-16等解码编码问题可参考博文:
从字节理解Unicode(UTF8/UTF16)
step2:
在C#中接收string,然后用UTF-8进行编码,如下:
//Write方法
AdsStream rStream = new AdsStream(100);
AdsBinaryReader reader = new AdsBinaryReader(rStream);
_client.Read(_myWstringHand, rStream);
rStream.Seek(0, System.IO.SeekOrigin.Begin);
//在倍福那边将WSTRING定义成UTF8格式的STRING,C#中以byte[]形式读取,然后以UTF8编码
rWstrtb.Text = Encoding.UTF8.GetString(reader.ReadBytes(81));
关于此处,本人也尝试用WriteAny进行实现,但是总会遇到编码解码不完全,或者写入TC3时覆盖不完全等问题,所以弃用了WriteAny方法,如有读者利用WriteAny测试成功,还请留言指点(抱拳)。
-
TC3与C#变量类型对应关系
5. 完整工程项目文件
链接:https://pan.baidu.com/s/12ly_fzMBRk7I5DQUUQTV6Q
提取码:aihx
后记:笔者才疏学浅,如有错误,望读者指正。