倍福控制器(Beckhoff Twincat 3)的ADS通讯相关知识及测试
ADS简介
YapethsDY 2020/08/27 PM
-
基于ADS的twincat系统架构
在Beckhoff TwinCAT 系统中,各个软件模块(如TwinCAT PLC、TwinCAT NC、Windows 应用程序等)的工作模式类似于硬件设备,它们能够独立工作,各个软件模块之间的信息交换通过TwinCAT ADS 而完成。因此各个ADS 设备之间都能够交换数据和信息。(获取与官方ppt)
-
ADS通讯协议概述
ADS通讯协议位于网络通讯协议的应用层
-
ADS通讯协议的设备标识
每台ADS设备都有各自不同的AdsAmsNetID和AdsPort端口号,也是其最重要的两个属性。
• AdsPort 指定通信的虚拟设备(ADS server),各不相同且固定不变,而ADS客户端应用程序的port则是可变的。创建plc程序的时候默认端口为851。
• AdsAmsNetId 指定ADS路由器,是TCP IP地址的扩展。一台PC的IP为“192.168.10.10”时,AdsAmsNetId就是“192.168.10.10.1.1”。
-
ADS通讯方式
- 异步方式(Asynchronous)
ADS 客户端向ADS 服务器发送ADS 请求,同时客户端继续自己的工作。ADS 服务器处理请求后,把响应以Call-back 函数方式发给客户端。
2.通知方式(Notification)
ADS 客户端向ADS 服务器发送ADS 请求,ADS 服务器以Call-back 函数的方式不断向客户端发送响应,直到客户端取消该请求。
这两种通讯方式的效率高,但需求复杂的客户端程序。
优点:不会造成系统堵塞。
缺点:不能确保每次请求都有返回。
3.同步方式(Synchronous)
ADS 客户端向ADS 服务器发送ADS 请求,在通讯过程中客户端程序停止执行,直到获得ADS 服务器返回的响应。
这种通讯方式不需求复杂的客户端程序,但其轮循的通讯方式给系统带来比较大的负载,因此通讯效率较低。
优点:能即时返回结果。
缺点:如果通讯故障会造成系统堵塞。
-
ADS访问变量的方式
1.地址方式
一个PLC变量的地址由两部分组成:GroupIndex和OffsetIndex,前者一般区别与寄存器类型,一般为常量。后者为变量的偏移地址,在PLC中为该变量的地址。
2.变量名方式
在TwinCAT ADS设备中每个变量都有一个句柄(Handle),适用变量名访问变量首先需要得到该变量的句柄。
测试环境:
- Twincat 3开发平台
- windows 7 环境
- VS 2015 c#开发IDE
首先明确下.net环境下的调用流程,ADS组件库的加载不做赘述
分两种方式来进行测试
/******************************************************************************
* ProjectName: AdsProject
* Description: ADS通讯类,主要完成以下功能
* 1.与ADS设备连接,断开
* 2.同步通讯(读/写变量)的方法
* ClassName: ADSInteractionHMI
* CLRVersion: .NET Framework 4.5及以上
* Author: YapethsDY
* NameSpace: ADSInteractionHMI
* MachineName: ThinkPad T460
* CreateTime: 2020/08/27 晚
* UpdatedTime: Null
* others: 未来待实现通过订阅方式获取变量值变更的方法
* 那样看起来不需要浪费多余的资源
* Others
* Copyright(C) All rights reserved
*******************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using TwinCAT.Ads;
namespace ADSInteractionHMI
{
class ADSOperation
{
private TcAdsClient ADSClient = new TcAdsClient() ;
//ADS通讯打开
public int ADSOpen(string[] adsTagsNameArray)
{
int iRet = -1;
int Index = 0;
ADSClient = new TcAdsClient();
try
{
ADSClient.Connect(ADS.AMSNetId, ADS.AMSNetPort);
for (Index = 0; Index < ADS.ADSTagsCount; Index++)
{
MessageBox.Show("句柄创建成功");
//创建变量存储指定的句柄
ADS.adsHandleArray[Index] =
ADSClient.CreateVariableHandle(adsTagsNameArray[Index]);
}
iRet = 0;
}
catch (System.Exception ex)
{
//异常日志记录
LogHelper.ErrorLog(null, ex);
iRet = -1;
}
return iRet;
}
//ADS读取
public int ADSRead(int Handle, ref object Value, Type type)
{
object tmp = 0;
int iRet = -1;
try
{
tmp = ADSClient.ReadAny(Handle, type);
iRet = 0;
}
catch (System.Exception ex)
{
iRet = -1;
Debug.WriteLine(ex.Message);
Debug.WriteLine("ADSClient.ReadAny异常");
MessageBox.Show(ex.Message);
}
if (0 == iRet)
{
Value = tmp;
}
return iRet;
}
//ADS写入
public int ADSWrite(int Handle, object Value)
{
int iRet = -1;
try
{
ADSClient.WriteAny(Handle, Value);
iRet = 0;
}
catch (System.Exception ex)
{
iRet = -1;
Debug.WriteLine(ex.Message);
Debug.WriteLine("ADSClient.WriteAny异常");
MessageBox.Show(ex.Message);
}
return iRet;
}
//ADS关闭
public int ADSClose()
{
int Index = 0;
try
{
//删除创建的句柄
for (Index = 0; Index < ADS.ADSTagsCount; Index++)
{
ADSClient.DeleteVariableHandle(ADS.adsHandleArray[Index]);
}
}
catch (System.Exception ex)
{
//异常日志记录
LogHelper.ErrorLog(null, ex);
}
finally
{
//资源释放
ADSClient.Dispose();
}
return 0;
}
}
}
//**********************************************************************************
class ADS{
public static bool ADSok = false; //ADS连接/断开状态
public const string AMSNetId = "192.168.10.10.1.1"; //本地AMSNetID
public const int AMSNetPort = 851; //建的PLC工程默认端口号
public const int ADSTagsCount = 10; //相关联的变量总数
public static ADSOperation ADSOperation = new ADSOperation();//ADS通讯客户端对象明
public static ADSCommunication ADSCommunication = new ADSCommunication();//ADS通讯
public static string[] adsTagsNameArray = new string[ADSTagsCount];//ADS变量的名称
public static int[] adsHandleArray = new int[ADSTagsCount]//所需读取的ADS变量句柄数组
public static LogRecord tcLogRecord = new LogRecord();//日志记录对象声明
}
//后续还需要初始化变量名与句柄的对应关系 比较冗余就不写了
这种写法虽然说简单,但资源浪费严重,需要在主线程或着界面刷新线程中添加定时器,有的不需要在很短周期内读取的变量也变得优先级很高,显得很浪费,明天会增加事件触发或者json注册的方式来读取,当然了还是依靠变量名来操作。
happy ending!
上一篇: Qt创建新窗口时局部静态变量的使用方式
下一篇: 【内存越界定位】magic number