Beckhoff ADS (Automation DeviceSpecification)提供一个应用程序之间互相通信的接口,在TW3系统中,TwinCAT PLC,TwinCAT NC等被设计成虚拟的自动化设备,类似于实际的物理设备与设备之间通过基于TCP协议的路由来交换信息,参考下面的图示
ADS设备最重要的两个属性就是端口号和AdsAmsNetId
• AdsPortNr 指定通信的虚拟设备(ADS server),比如PLC,NC
• AdsAmsNetId 指定ADS路由器,是TCP IP地址的扩展
既然是基于TCP协议,那对照TCP/IP七层协议如下
接着看下ADS协议/报文的数据结构
在AMS Header中存放着上面提到的AMS NET ID和PORT
还有从PLC Read和Write变量时存放的变量地址和偏移量
ADS 设备之间的通讯有多种方式,不同方式有不同的特点。
- 异步方式(Asynchronous)
ADS 客户端向ADS 服务器发送ADS 请求,同时客户端继续自己的工作。ADS 服务器处理请求后,把响应以Call-back 函数方式发给客户端。
- 通知方式(Notification)
ADS 客户端向ADS 服务器发送ADS 请求,ADS 服务器以Call-back 函数的方式不断向客户端发送响应,直到客户端取消该请求。PLC变量不更新,就不会向客户端相应,类似于OPC通信的订阅。
这两种通讯方式的效率高,但需求复杂的客户端程序。
优点:不会造成系统堵塞
缺点:不能确保每次请求都有返回TwinCATADS 设备和Windows应用程序(例如VB、VC 应用程序等)之间的通讯除了可以采用一般的ADS 通讯方式外,还可以采用特殊的通讯方式,即同步通讯方式。
- 同步方式(Synchronous)
ADS 客户端向ADS 服务器发送ADS 请求,在通讯过程中客户端程序停止执行,直到获得ADS 服务器返回的响应。
这种通讯方式不需求复杂的客户端程序,但其轮循的通讯方式给系统带来比较大的负载,因此通讯效率较低。
优点:能即时返回结果
缺点:如果通讯故障会造成系统堵塞
TwinCAT ADS访问变量有两种方式:
1.地址方式
一个PLC变量的地址由两部分组成:GroupIndex和OffsetIndex:
GroupIndex一般用于区别寄存器类型,在TwinCAT ADS设备中为常量,具体内容可以参考Information System(后附常用的GroupIndex值)。
OffsetIndex为变量的偏移地址,在PLC中为该变量的地址。
2.变量名方式
在TwinCAT ADS设备中每个变量都有一个句柄(Handle)。
适用变量名访问变量首先需要得到该变量的句柄。(不同的高级语言方式略有不同)
测试中使用的异步通讯方式,通过变量名方式,代码如下
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;using TwinCAT.Ads;namespace TW3_ADS_test
{public partial class Form1 : Form{private TcAdsClient tcAdsClient; //会有变量定义在类外面吗,比如说在CLASS Form1外面定义变量,共几个类用???private int hander1, hander2,hd1,hd2,hd3,hd4;private AdsStream iStream;private AdsStream bStream;private AdsStream sStream;private AdsBinaryReader iBinaryReader;private AdsBinaryReader bBinaryReader;private AdsBinaryReader sBinaryReader;private AdsBinaryWriter sBinaryWriter;private bool[] b;/// <summary>/// Form1窗口构造函数,窗口初始化/// </summary>public Form1(){InitializeComponent();iStream = new AdsStream(3 * 2);//流数据必须赋值大小,否则报错:无法在流的结尾之外进行读取bStream = new AdsStream(3);sStream = new AdsStream(7);iBinaryReader = new AdsBinaryReader(iStream);bBinaryReader = new AdsBinaryReader(bStream);sBinaryReader = new AdsBinaryReader(sStream);sBinaryWriter = new AdsBinaryWriter(sStream);try{Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851tcAdsClient = new TcAdsClient();tcAdsClient.Connect(851);hander1 = tcAdsClient.CreateVariableHandle("MAIN.ivar");hander2 = tcAdsClient.CreateVariableHandle("MAIN.bvar");hd1 = tcAdsClient.CreateVariableHandle("MAIN.bvar[0]");hd2 = tcAdsClient.CreateVariableHandle("MAIN.bvar[1]");hd3 = tcAdsClient.CreateVariableHandle("MAIN.bvar[2]");hd4 = tcAdsClient.CreateVariableHandle("MAIN.svar");}catch(Exception ex){MessageBox.Show("ADS设备连接失败,请确认变量名和PLC运行状态:"+ex.Message );}}/// <summary>/// 圆形区域重绘/// </summary>private void lightstate(){Graphics g;SolidBrush brush = new SolidBrush(Color.Green);SolidBrush redbrush = new SolidBrush(Color .Red );g = CreateGraphics();if (b[0])g.FillEllipse(brush, 80, 80, 40, 40);elseg.FillEllipse(redbrush ,80,80,40,40);if (b[1])g.FillEllipse(brush, 220, 80, 40, 40);elseg.FillEllipse(redbrush, 220, 80, 40, 40);if (b[2])g.FillEllipse(brush, 350, 80, 40, 40);elseg.FillEllipse(redbrush, 350, 80, 40, 40);}/// <summary>/// 初始化状态圆形区域/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void Form1_Paint(object sender, EventArgs e){Graphics g;SolidBrush brush = new SolidBrush(Color.Red);//Pen pen = new Pen( Color.Red,1); //画空心圆g = CreateGraphics();g.FillEllipse(brush,80,80,40,40);g.FillEllipse(brush, 220, 80, 40, 40);g.FillEllipse(brush, 350, 80, 40, 40);//g.DrawEllipse (pen,400,80,40,40);}/// <summary>/// 窗口Load事件/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void Form1_Load(object sender, EventArgs e){this .Paint +=new PaintEventHandler(Form1_Paint);//不是在VS里双击过来的事件,可以手动添加委托timer1.Interval = 100;button4.Enabled = true;button5.Enabled = false;listBox1.Items.Clear();}private void timer1_Tick(object sender, EventArgs e){b = new bool[3];try{tcAdsClient.Read(hander1, iStream);tcAdsClient.Read(hander2, bStream);tcAdsClient.Read( hd4 ,sStream );iStream.Position = 0; //否则报错:无法在流的结尾之外进行读取textBox1.Text = iBinaryReader.ReadInt16().ToString();textBox2.Text = iBinaryReader.ReadInt16().ToString();textBox3.Text = iBinaryReader.ReadInt16().ToString();bStream.Position = 0;b[0] = bBinaryReader.ReadBoolean();b[1] = bBinaryReader.ReadBoolean();b[2] = bBinaryReader.ReadBoolean();sStream.Position = 0;//TwinCAT.Ads.NET version >= 1.0.0.10: new method//listBox1.Items.Add( sBinaryReader .ReadPlcString(4) );//TwinCAT.Ads.NET version < 1.0.0.10:old methodlistBox1.Items.Add(tcAdsClient .ReadAny ( hd4 ,typeof ( string ),new int []{7}));}catch( Exception ex ){MessageBox.Show("读PLC数据错误"+ex.Message );}lightstate();}/// <summary>/// 开始读数据从PLC/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void button4_Click(object sender, EventArgs e){sBinaryWriter.WritePlcString( textBox1 .Text ,7 );tcAdsClient.Write( hd4 ,sStream );timer1.Enabled = true;button4.Enabled = false;button5.Enabled = true;}/// <summary>/// 停止和PLC的ADS通信/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void button5_Click(object sender, EventArgs e){timer1.Enabled = false;button4.Enabled = true;button5.Enabled = false;}private void button1_Click(object sender, EventArgs e){if (b[0])b[0] = false; elseb[0] = true;}private void button2_Click(object sender, EventArgs e){if (b[1])b[1] = false;elseb[1] = true;tcAdsClient.WriteAny( hd2 ,b[1] );}private void button3_Click(object sender, EventArgs e){if (b[2])b[2] = false;elseb[2] = true;}}
}
PLC代码:
PROGRAM MAIN
VARivar:ARRAY[0..2]OF INT;bvar:ARRAY[0..2]OF BOOL;index:INT;svar:STRING:='test';
END_VAR
FOR index:=0 TO 2 DOivar[index]:=500+index;
END_FOR
bvar[0]:=TRUE;
bvar[2]:=TRUE;